chore(openspec): archive add-group-buying

Sync the group-buying 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. This closes the last of the five
planned marketing changes.
This commit is contained in:
2026-09-18 13:22:47 +00:00
parent 48ec5b4443
commit cdfb1b06f2
12 changed files with 97 additions and 16 deletions
@@ -0,0 +1,16 @@
## ADDED Requirements
### Requirement: Live group-buying page
The mall SHALL render active group-buying activities and open groups from the shared API client, including group price, required member count, current paid-member count, and expiry. It SHALL let an authenticated shopper choose to open or join a group and replace derived fixture counts. Checkout SHALL submit the open-or-join intent for a single activity SKU at quantity 1. Payment and order-detail views SHALL show the snapshotted group price. This change SHALL NOT add group price or join CTAs to the catalog product-detail page (`/goods/[id]`); opening and joining happen from the group-buying page and checkout intent.
#### Scenario: open a group
- **WHEN** a shopper selects an active group-buying product and chooses to open a group
- **THEN** the checkout intent identifies the activity and requests a new group
#### Scenario: order shows group snapshot
- **WHEN** checkout succeeds with group-buy intent
- **THEN** payment and order-detail views show the server group unit price from the order payload
#### Scenario: product detail stays on catalog price
- **WHEN** a shopper opens a catalog product that also has an active group-buying activity
- **THEN** the product-detail page still shows catalog SKU prices and does not require a group-buy overlay in this change
@@ -0,0 +1,12 @@
## ADDED Requirements
### Requirement: Merchant group-buying management
Shop users SHALL manage only their shop's group-buying activities in shop-admin, configuring an owned SKU, localized display content, minor-unit group price, required paid-member count, active window, and group lifetime. Shop-admin SHALL expose a group-buying navigation entry beside existing shop operations.
#### Scenario: publish activity
- **WHEN** a merchant publishes a valid group-buying activity
- **THEN** shoppers can discover it during its active window
#### Scenario: group buying appears in shop navigation
- **WHEN** an authenticated shop user opens shop-admin
- **THEN** a group-buying management entry is reachable without leaving the shop-scoped console
@@ -0,0 +1,34 @@
## ADDED Requirements
### Requirement: Shop group-buying activities
Shop users SHALL manage only their timed group-buying activities, each bound to one owned SKU with localized display content, fixed minor-unit group price, required paid-member count of at least two, group lifetime, and a per-order activity quantity of 1. The service SHALL reject a SKU that already has an overlapping active flash-sale item.
#### Scenario: invalid participant target
- **WHEN** a merchant configures a group activity requiring fewer than two paid members
- **THEN** the API rejects the activity
#### Scenario: overlapping flash-sale SKU rejected
- **WHEN** a merchant publishes a group activity for a SKU that already has an overlapping active flash-sale item
- **THEN** the API rejects the activity
### Requirement: Customer group discovery and intent
Customers SHALL discover active group-buying activities and open groups. A customer MAY initiate checkout by opening a new group or selecting an open eligible group for exactly one activity SKU at quantity 1.
#### Scenario: join an open group
- **WHEN** a shopper selects an active open group and checks out its activity SKU
- **THEN** the resulting pending-payment order identifies that group and snapshots the group price
### Requirement: Group membership and lifecycle
Payment SHALL atomically claim a paid membership in an open unexpired group. A group SHALL become successful exactly when paid member count reaches its required count; it SHALL become expired when its lifetime ends before success; it SHALL become cancelled when its opening pending-payment order is cancelled while paid member count is zero. A full, expired, or cancelled group SHALL reject payment without changing order state.
#### Scenario: concurrent final seat
- **WHEN** two pending group orders attempt payment for one final seat concurrently
- **THEN** exactly one payment succeeds and the group becomes successful
#### Scenario: expired paid group
- **WHEN** an open group expires before reaching its required count
- **THEN** it is marked expired and its paid orders remain identifiable for a later refund flow
#### Scenario: unpaid opener cancel closes an empty group
- **WHEN** the opening pending-payment order is cancelled and the group still has zero paid members
- **THEN** the group becomes cancelled, SKU stock is restored, and later join or payment for that group returns 409
@@ -0,0 +1,55 @@
## MODIFIED 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, active flash-sale resolution when no group-buy intent applies to the SKU, requested group-buying intent validation, and requested coupon redemption when eligible. All amounts use the cart's SKU currencies converted into the buyer-chosen display currency at checkout time. A group-buy intent SHALL contain exactly one active activity SKU at quantity 1 and either an open group identifier or a request to open a group; the server SHALL snapshot the group price and group identity on the resulting pending-payment order. Group intent SHALL return 409 when that SKU is also eligible for an overlapping flash sale. For each remaining cart line without group intent the server SHALL resolve an eligible active flash-sale item as specified by flash-sales, snapshot the final unit price and activity identity, and charge standard price for ineligible quantity. The request MAY select at most one owned coupon per generated shop order; the server SHALL persist `coupon_id`, `discount_minor`, and post-discount `total_minor` only after ownership, shop, status, window, and threshold validation. The client SHALL NOT provide a discount amount. The server SHALL reject a coupon (409) for any shop order that applied flash-sale pricing or carries group-buy intent.
#### Scenario: two shops → two orders
- **WHEN** the cart contains SKUs from shops A and B without group-buy intent
- **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 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
#### Scenario: coupon rejected on a group shop order
- **WHEN** the customer selects a coupon for the shop that carries group-buy intent
- **THEN** checkout returns 409 and creates no orders
#### Scenario: coupon applies to one matching shop
- **WHEN** the customer selects an eligible shop coupon and that shop's generated order has no flash-priced lines and no group-buy intent
- **THEN** the order records that coupon and its server-calculated discount
### Requirement: Order lifecycle
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. Pending-payment cancellation SHALL NOT change group paid-member counts. When the cancelled order opened the group and paid member count is zero, the group SHALL become cancelled. Payment of an order with group-buying intent SHALL also validate and atomically claim an open group seat; full, expired, or cancelled groups SHALL return 409 and leave the order pending_payment.
#### Scenario: group payment wins final seat
- **WHEN** payment fills the final available group seat
- **THEN** the payment succeeds and the group state becomes successful
#### Scenario: cancel restores stock
- **WHEN** a customer cancels a pending_payment group-intent order
- **THEN** SKU stock increases by the ordered qty, status is cancelled, and no paid group membership is removed
#### Scenario: unpaid opener cancel closes an empty group
- **WHEN** the opening pending_payment order is cancelled and the group has zero paid members
- **THEN** the group status becomes cancelled and a later payment against that group returns 409
#### Scenario: cancel restores coupon
- **WHEN** a customer cancels a pending_payment order with a redeemed coupon
- **THEN** the coupon is detached from the order and becomes claimed
#### Scenario: illegal transition rejected
- **WHEN** cancelling a paid order via the customer endpoint
- **THEN** the API returns 409