From cc07ac3230d2519ce7b3a3a8e795b3195c9db276 Mon Sep 17 00:00:00 2001 From: Zhang Chengdong Date: Fri, 18 Sep 2026 12:13:49 +0000 Subject: [PATCH] chore(openspec): archive add-shop-coupons Sync the shop-coupons 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. Renaming the coupon scenario in the flash-sales and group-buying order deltas to the exact name now in the main order spec keeps those changes archivable, since a MODIFIED requirement replaces the whole block. --- .../add-flash-sales/specs/order/spec.md | 2 +- .../add-group-buying/specs/order/spec.md | 2 +- .../.openspec.yaml | 0 .../2026-09-18-add-shop-coupons}/design.md | 0 .../2026-09-18-add-shop-coupons}/proposal.md | 0 .../specs/frontend-mall/spec.md | 0 .../specs/frontend-shop-admin/spec.md | 0 .../specs/order/spec.md | 0 .../specs/shop-coupons/spec.md | 0 .../2026-09-18-add-shop-coupons}/tasks.md | 10 +++---- openspec/specs/frontend-mall/spec.md | 15 +++++++++++ openspec/specs/frontend-shop-admin/spec.md | 12 +++++++++ openspec/specs/order/spec.md | 15 ++++++++--- openspec/specs/shop-coupons/spec.md | 27 +++++++++++++++++++ 14 files changed, 73 insertions(+), 10 deletions(-) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/.openspec.yaml (100%) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/design.md (100%) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/proposal.md (100%) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/specs/frontend-mall/spec.md (100%) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/specs/frontend-shop-admin/spec.md (100%) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/specs/order/spec.md (100%) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/specs/shop-coupons/spec.md (100%) rename openspec/changes/{add-shop-coupons => archive/2026-09-18-add-shop-coupons}/tasks.md (81%) create mode 100644 openspec/specs/shop-coupons/spec.md diff --git a/openspec/changes/add-flash-sales/specs/order/spec.md b/openspec/changes/add-flash-sales/specs/order/spec.md index a7871db..7ee3162 100644 --- a/openspec/changes/add-flash-sales/specs/order/spec.md +++ b/openspec/changes/add-flash-sales/specs/order/spec.md @@ -15,7 +15,7 @@ - **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 a shop without flash lines +#### 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 - **THEN** the order records that coupon and its server-calculated discount diff --git a/openspec/changes/add-group-buying/specs/order/spec.md b/openspec/changes/add-group-buying/specs/order/spec.md index d61547d..da0b001 100644 --- a/openspec/changes/add-group-buying/specs/order/spec.md +++ b/openspec/changes/add-group-buying/specs/order/spec.md @@ -19,7 +19,7 @@ - **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 a shop without activity prices +#### 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 diff --git a/openspec/changes/add-shop-coupons/.openspec.yaml b/openspec/changes/archive/2026-09-18-add-shop-coupons/.openspec.yaml similarity index 100% rename from openspec/changes/add-shop-coupons/.openspec.yaml rename to openspec/changes/archive/2026-09-18-add-shop-coupons/.openspec.yaml diff --git a/openspec/changes/add-shop-coupons/design.md b/openspec/changes/archive/2026-09-18-add-shop-coupons/design.md similarity index 100% rename from openspec/changes/add-shop-coupons/design.md rename to openspec/changes/archive/2026-09-18-add-shop-coupons/design.md diff --git a/openspec/changes/add-shop-coupons/proposal.md b/openspec/changes/archive/2026-09-18-add-shop-coupons/proposal.md similarity index 100% rename from openspec/changes/add-shop-coupons/proposal.md rename to openspec/changes/archive/2026-09-18-add-shop-coupons/proposal.md diff --git a/openspec/changes/add-shop-coupons/specs/frontend-mall/spec.md b/openspec/changes/archive/2026-09-18-add-shop-coupons/specs/frontend-mall/spec.md similarity index 100% rename from openspec/changes/add-shop-coupons/specs/frontend-mall/spec.md rename to openspec/changes/archive/2026-09-18-add-shop-coupons/specs/frontend-mall/spec.md diff --git a/openspec/changes/add-shop-coupons/specs/frontend-shop-admin/spec.md b/openspec/changes/archive/2026-09-18-add-shop-coupons/specs/frontend-shop-admin/spec.md similarity index 100% rename from openspec/changes/add-shop-coupons/specs/frontend-shop-admin/spec.md rename to openspec/changes/archive/2026-09-18-add-shop-coupons/specs/frontend-shop-admin/spec.md diff --git a/openspec/changes/add-shop-coupons/specs/order/spec.md b/openspec/changes/archive/2026-09-18-add-shop-coupons/specs/order/spec.md similarity index 100% rename from openspec/changes/add-shop-coupons/specs/order/spec.md rename to openspec/changes/archive/2026-09-18-add-shop-coupons/specs/order/spec.md diff --git a/openspec/changes/add-shop-coupons/specs/shop-coupons/spec.md b/openspec/changes/archive/2026-09-18-add-shop-coupons/specs/shop-coupons/spec.md similarity index 100% rename from openspec/changes/add-shop-coupons/specs/shop-coupons/spec.md rename to openspec/changes/archive/2026-09-18-add-shop-coupons/specs/shop-coupons/spec.md diff --git a/openspec/changes/add-shop-coupons/tasks.md b/openspec/changes/archive/2026-09-18-add-shop-coupons/tasks.md similarity index 81% rename from openspec/changes/add-shop-coupons/tasks.md rename to openspec/changes/archive/2026-09-18-add-shop-coupons/tasks.md index 7d4fa84..28845ed 100644 --- a/openspec/changes/add-shop-coupons/tasks.md +++ b/openspec/changes/archive/2026-09-18-add-shop-coupons/tasks.md @@ -13,11 +13,11 @@ ## 3. Merchant and customer surfaces -- [ ] 3.1 Add shop-admin coupon-template management and a coupons nav entry scoped to the authenticated shop. -- [ ] 3.2 Replace mall product-detail coupon strip, buyer coupon list, and checkout coupon selection with shared live-client calls; show persisted discount on payment and order-detail views. -- [ ] 3.3 Remove the affected direct coupon fixture imports while retaining the complete fixed-data adapter rollback surface. +- [x] 3.1 Add shop-admin coupon-template management and a coupons nav entry scoped to the authenticated shop. +- [x] 3.2 Replace mall product-detail coupon strip, buyer coupon list, and checkout coupon selection with shared live-client calls; show persisted discount on payment and order-detail views. +- [x] 3.3 Remove the affected direct coupon fixture imports while retaining the complete fixed-data adapter rollback surface. ## 4. Verification and specification -- [ ] 4.1 Seed deterministic coupon templates and verify the live claim-to-checkout-to-cancel flow against the API. -- [ ] 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 deterministic coupon templates and verify the live claim-to-checkout-to-cancel flow against the API. +- [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/frontend-mall/spec.md b/openspec/specs/frontend-mall/spec.md index 1dc7354..0122516 100644 --- a/openspec/specs/frontend-mall/spec.md +++ b/openspec/specs/frontend-mall/spec.md @@ -159,3 +159,18 @@ The buyer-center dashboard and points surfaces SHALL load the signed-in customer #### Scenario: account summary reload - **WHEN** a signed-in shopper reloads the buyer center - **THEN** displayed account statistics come from the selected API adapter and survive browser state loss + +### Requirement: Live shop coupon surfaces +The mall SHALL load a product's currently claimable shop coupons and the signed-in customer's coupon list through the shared API client, replacing direct coupon fixtures. Checkout SHALL present eligible owned coupons separately for each shop order and show the server-returned discount after submission. Payment and order-detail views SHALL show the persisted `discount_minor` and post-discount total from the order payload. + +#### Scenario: claim from a product page +- **WHEN** a signed-in shopper claims an active coupon with remaining stock from a product page +- **THEN** it appears in the shopper's coupon list without a fixture import + +#### Scenario: select coupon by shop +- **WHEN** a cart spans two shops and the shopper selects a coupon for one shop +- **THEN** checkout submits the choice only for that shop and the other shop remains undiscounted + +#### Scenario: order shows realized discount +- **WHEN** checkout succeeds with a coupon on one shop order +- **THEN** that order's payment and detail views show the server discount and reduced total without a client-calculated amount diff --git a/openspec/specs/frontend-shop-admin/spec.md b/openspec/specs/frontend-shop-admin/spec.md index b6cd32d..6e6b314 100644 --- a/openspec/specs/frontend-shop-admin/spec.md +++ b/openspec/specs/frontend-shop-admin/spec.md @@ -2,7 +2,9 @@ ## Purpose The merchant console for managing a shop's products and fulfilling its orders. + ## Requirements + ### Requirement: Merchant product management Shop users SHALL manage only their own shop's products: create/edit bilingual content, manage SKUs, publish/unpublish with immediate effect on the storefront. @@ -17,3 +19,13 @@ Shop users SHALL see incoming orders, create shipments, mark them shipped, and i - **WHEN** a merchant creates a shipment for a paid order and marks it shipped - **THEN** the shopper sees the shipment with tracking info +### Requirement: Merchant coupon management +Shop users SHALL manage only their shop's coupon templates from shop-admin, including localized title, amount and threshold in minor units, currency, claim stock, active window, and enabled state. Shop-admin SHALL expose a coupons navigation entry beside existing shop operations. + +#### Scenario: create an active template +- **WHEN** a shop owner creates a valid coupon template +- **THEN** the template is available for eligible customers to claim during its active window + +#### 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 diff --git a/openspec/specs/order/spec.md b/openspec/specs/order/spec.md index 6dec099..8511e34 100644 --- a/openspec/specs/order/spec.md +++ b/openspec/specs/order/spec.md @@ -2,9 +2,11 @@ ## Purpose Checkout splitting a cart into per-shop orders, plus order lifecycle and ownership. + ## 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. All amounts use the cart's SKU currencies converted into the buyer-chosen display currency at checkout time. +`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. #### Scenario: two shops → two orders - **WHEN** the cart contains SKUs from shops A and B @@ -14,13 +16,21 @@ Checkout splitting a cart into per-shop orders, plus order lifecycle and ownersh - **WHEN** any line's qty exceeds SKU stock - **THEN** the whole checkout returns 409 and no order is created and stock is unchanged +#### 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 + ### Requirement: Order lifecycle -Status transitions SHALL be: pending_payment → paid → fulfilling → shipped → completed; cancellable only from pending_payment, which MUST restore stock. +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. #### 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 +#### 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 @@ -31,4 +41,3 @@ Customers SHALL see only their own orders; shop roles only their shop's orders; #### Scenario: cross-customer read denied - **WHEN** customer X requests customer Y's order id - **THEN** the API returns 404 - diff --git a/openspec/specs/shop-coupons/spec.md b/openspec/specs/shop-coupons/spec.md new file mode 100644 index 0000000..8b56e6c --- /dev/null +++ b/openspec/specs/shop-coupons/spec.md @@ -0,0 +1,27 @@ +# shop-coupons Specification + +## Purpose +Let a shop issue its own coupons and a customer claim and redeem them. A template owns the offer, window, and claim stock; claiming copies the terms into a customer-owned snapshot so later edits cannot rewrite a held coupon. Checkout redeems at most one owned coupon per generated shop order, applies a server-calculated discount, and restores the coupon when a pending-payment order is cancelled. Activity-priced shop orders reject coupons rather than stack. + +## Requirements + +### Requirement: Shop coupon template management +A shop_owner or shop_staff SHALL create, list, update, disable, and delete only that shop's coupon templates. A template SHALL contain localized title, fixed `amount_minor`, `threshold_minor`, enabled ISO currency, non-negative claim stock, and an inclusive active window; amount MUST be positive and threshold MUST NOT be negative. + +#### Scenario: cross-shop template is hidden +- **WHEN** a shop user requests or mutates another shop's coupon template +- **THEN** the API returns 404 + +### Requirement: Customer coupon claim and listing +An authenticated customer SHALL list owned coupons and claim an active template at most once. Claiming SHALL atomically create a user-held snapshot and decrement claim stock only when stock remains; listing SHALL distinguish claimed, redeemed, and expired coupons. + +#### Scenario: concurrent final claim +- **WHEN** two customers claim the final remaining template instance concurrently +- **THEN** exactly one claim succeeds and the other receives 409 + +### Requirement: Coupon redemption lifecycle +A claimed coupon SHALL be redeemable only by its owner, for its issuing shop, within its snapshot window, and when the shop-order subtotal reaches its snapshot threshold. Redemption SHALL bind the coupon to one order; cancellation of that pending-payment order SHALL restore it to claimed. Later activity-priced shop orders (flash sale or group buy) SHALL NOT redeem a coupon; those changes restated this exclusion on checkout. + +#### Scenario: cancelled order restores coupon +- **WHEN** a customer cancels a pending-payment order redeemed with a coupon +- **THEN** the coupon again has claimed status and no order binding