feat: three nuxt frontends, demo seed, rounding + money-exponent + rate-cast fixes, archived specs
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# auth Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change foundation-auth. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Customer registration
|
||||
The API SHALL provide `POST /api/auth/register` accepting email, password, display_name. New users are created with role `customer`. Duplicate emails MUST be rejected with 409.
|
||||
|
||||
#### Scenario: successful registration
|
||||
- **WHEN** a client posts a unique email with password ≥ 8 chars
|
||||
- **THEN** the API returns 201 with `{ token, user }` and the user can call `/api/auth/me` with the token
|
||||
|
||||
#### Scenario: duplicate email
|
||||
- **WHEN** the email already exists
|
||||
- **THEN** the API returns 409 with code `CONFLICT`
|
||||
|
||||
### Requirement: Login
|
||||
The API SHALL provide `POST /api/auth/login` issuing a signed JWT (24h TTL) containing user id and role.
|
||||
|
||||
#### Scenario: valid credentials
|
||||
- **WHEN** email + correct password are posted
|
||||
- **THEN** the API returns `{ token, user }`
|
||||
|
||||
#### Scenario: invalid credentials
|
||||
- **WHEN** the password is wrong or email unknown
|
||||
- **THEN** the API returns 401 with code `UNAUTHORIZED` and no token
|
||||
|
||||
### Requirement: Current user
|
||||
`GET /api/auth/me` SHALL return the authenticated user profile.
|
||||
|
||||
#### Scenario: missing token
|
||||
- **WHEN** no Bearer token is supplied
|
||||
- **THEN** the API returns 401
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# cart Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change cart-checkout-orders. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Server-side cart
|
||||
Authenticated shoppers SHALL have a Redis-backed cart keyed by user id, containing sku_id + qty entries.
|
||||
|
||||
#### Scenario: add and update
|
||||
- **WHEN** a shopper POSTs sku + qty, then PUTs a new qty
|
||||
- **THEN** GET /api/cart reflects the latest qty with current price/name snapshot
|
||||
|
||||
#### Scenario: unpurchasable SKU rejected
|
||||
- **WHEN** adding a SKU that is inactive or whose product is not published
|
||||
- **THEN** the API returns 400
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# catalog Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change catalog-i18n-currency. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Localized product content
|
||||
Product and category names/descriptions SHALL be stored as JSONB maps keyed by locale (`en`, `zh`). The API MUST return the full map; clients pick the display locale.
|
||||
|
||||
#### Scenario: bilingual round-trip
|
||||
- **WHEN** a shop owner creates a product with name `{"en": "Mug", "zh": "马克杯"}`
|
||||
- **THEN** both public detail and shop-admin GET return the identical map
|
||||
|
||||
### Requirement: Publish lifecycle
|
||||
Products SHALL have status `draft | published | unpublished`. Only `published` products appear in public list/detail.
|
||||
|
||||
#### Scenario: publish then unpublish
|
||||
- **WHEN** a product is published
|
||||
- **THEN** it appears in `GET /api/products`
|
||||
- **WHEN** it is unpublished
|
||||
- **THEN** public detail returns 404 and it disappears from listings
|
||||
|
||||
#### Scenario: publish requires sellable SKU
|
||||
- **WHEN** publishing a product with no active SKU having price > 0
|
||||
- **THEN** the API returns 400
|
||||
|
||||
### Requirement: Shop isolation
|
||||
Shop-role users SHALL only see and mutate their own shop's products under `/api/shop/products`.
|
||||
|
||||
#### Scenario: cross-shop access denied
|
||||
- **WHEN** shop owner A requests `/api/shop/products/{id}` of shop B
|
||||
- **THEN** the API returns 404
|
||||
|
||||
### Requirement: SKU pricing
|
||||
Each SKU SHALL carry `price_minor` (integer minor units) and an ISO `currency` code; stock is a non-negative integer.
|
||||
|
||||
#### Scenario: negative stock rejected
|
||||
- **WHEN** upserting a SKU with stock < 0
|
||||
- **THEN** the API returns 400
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# currency Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change catalog-i18n-currency. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Currency registry
|
||||
The system SHALL maintain a currencies table: ISO code, localized name, symbol, exponent (minor units), enabled flag, and `rate_to_base` (NUMERIC). Exactly one currency is the base.
|
||||
|
||||
#### Scenario: seeded currencies
|
||||
- **WHEN** migrations finish
|
||||
- **THEN** USD exists as base with rate 1, and CNY/EUR/JPY exist with positive rates
|
||||
|
||||
### Requirement: Amount conversion
|
||||
`GET /api/currencies/convert` SHALL convert integer minor units between enabled currencies via base rates, rounding half-up to the target exponent.
|
||||
|
||||
#### Scenario: USD to JPY
|
||||
- **WHEN** converting 1000 minor USD (=$10.00) to JPY with rate 150
|
||||
- **THEN** the result is 1500 minor JPY (¥1500), an integer
|
||||
|
||||
#### Scenario: disabled currency rejected
|
||||
- **WHEN** converting to a disabled or unknown currency
|
||||
- **THEN** the API returns 400
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# frontend-admin Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change frontend-apps. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Platform user and shop management
|
||||
Platform admins SHALL assign user roles (with shop scope), create shops, and suspend/activate shops. Suspended shops' products MUST NOT be purchasable (enforced by API, reflected in UI).
|
||||
|
||||
#### Scenario: assign shop owner
|
||||
- **WHEN** an admin assigns role shop_owner with a shop to a user
|
||||
- **THEN** that user can log into shop-admin and manage that shop
|
||||
|
||||
### Requirement: Currency management
|
||||
Platform admins SHALL view currencies and update exchange rates; new rates affect subsequent conversions.
|
||||
|
||||
#### Scenario: rate update
|
||||
- **WHEN** an admin updates CNY rate_to_base
|
||||
- **THEN** the mall conversion endpoint returns amounts computed with the new rate
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# frontend-mall Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change frontend-apps. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Localized storefront
|
||||
The mall SHALL render UI strings and catalog content in en or zh from one switcher, defaulting to en.
|
||||
|
||||
#### Scenario: switch to Chinese
|
||||
- **WHEN** a shopper switches locale to zh
|
||||
- **THEN** navigation, buttons and product names render in Chinese without reload errors
|
||||
|
||||
### Requirement: Multi-currency display
|
||||
The mall SHALL offer a currency switcher (enabled currencies from the API) converting SKU prices for display; checkout uses the selected currency.
|
||||
|
||||
#### Scenario: switch currency
|
||||
- **WHEN** a shopper switches from USD to JPY on a product priced $10.00
|
||||
- **THEN** the displayed price reflects the API conversion rate (integer minor units)
|
||||
|
||||
### Requirement: Shopping flow
|
||||
A shopper SHALL be able to browse, view detail, add to cart, checkout with a shipping address, pay (mock), track shipments, confirm delivery, and request an invoice — all against the live API.
|
||||
|
||||
#### Scenario: end-to-end purchase
|
||||
- **WHEN** a registered shopper completes checkout on a non-empty cart
|
||||
- **THEN** orders appear under Orders and the cart is empty
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# frontend-shop-admin Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change frontend-apps. Update Purpose after archive.
|
||||
## 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.
|
||||
|
||||
#### Scenario: publish visible in mall
|
||||
- **WHEN** a merchant publishes a product in shop-admin
|
||||
- **THEN** it appears in the mall product list for the matching locale
|
||||
|
||||
### Requirement: Merchant fulfillment
|
||||
Shop users SHALL see incoming orders, create shipments, mark them shipped, and issue requested invoices.
|
||||
|
||||
#### Scenario: ship an order
|
||||
- **WHEN** a merchant creates a shipment for a paid order and marks it shipped
|
||||
- **THEN** the shopper sees the shipment with tracking info
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# invoice Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change fulfillment-invoices. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Invoice request
|
||||
Customers SHALL request at most one open invoice (发票) per order, with title, kind (`personal | company`), and tax_no required for company invoices. Amount equals the order total in the order currency.
|
||||
|
||||
#### Scenario: company invoice requires tax number
|
||||
- **WHEN** requesting a company invoice without tax_no
|
||||
- **THEN** the API returns 400
|
||||
|
||||
#### Scenario: duplicate rejected
|
||||
- **WHEN** an order already has a requested or issued invoice
|
||||
- **THEN** a second request returns 409
|
||||
|
||||
### Requirement: Invoice issuance
|
||||
Merchants SHALL issue requested invoices of their own shop's orders; issuing sets invoice_no, issued_at and status `issued`.
|
||||
|
||||
#### Scenario: issue flow
|
||||
- **WHEN** the shop issues a requested invoice
|
||||
- **THEN** the customer sees status `issued` with an invoice number
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# order Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change cart-checkout-orders. Update Purpose after archive.
|
||||
## 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.
|
||||
|
||||
#### 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
|
||||
- **THEN** the whole checkout returns 409 and no order is created and stock is unchanged
|
||||
|
||||
### Requirement: Order lifecycle
|
||||
Status transitions SHALL be: pending_payment → paid → fulfilling → shipped → completed; cancellable only from pending_payment, which MUST restore stock.
|
||||
|
||||
#### 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: illegal transition rejected
|
||||
- **WHEN** cancelling a paid order via the customer endpoint
|
||||
- **THEN** the API returns 409
|
||||
|
||||
### Requirement: Order ownership
|
||||
Customers SHALL see only their own orders; shop roles only their shop's orders; platform_admin sees all.
|
||||
|
||||
#### Scenario: cross-customer read denied
|
||||
- **WHEN** customer X requests customer Y's order id
|
||||
- **THEN** the API returns 404
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# rbac Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change foundation-auth. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Role model
|
||||
The system SHALL support roles `platform_admin`, `shop_owner`, `shop_staff`, `customer`. Shop roles MUST carry a `shop_id` scope.
|
||||
|
||||
#### Scenario: seeded platform admin
|
||||
- **WHEN** migrations run on a fresh database
|
||||
- **THEN** a `platform_admin` account exists and can log in
|
||||
|
||||
### Requirement: Role enforcement
|
||||
Protected routes SHALL declare required roles; the API MUST reject requests with insufficient role using 403.
|
||||
|
||||
#### Scenario: customer hits admin route
|
||||
- **WHEN** a `customer` token calls an `/api/admin/*` route
|
||||
- **THEN** the API returns 403 with code `FORBIDDEN`
|
||||
|
||||
#### Scenario: shop scope isolation
|
||||
- **WHEN** a `shop_owner` of shop A accesses `/api/shop/*` resources of shop B
|
||||
- **THEN** the API returns 403 or 404, never the data
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# shipment Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change fulfillment-invoices. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Shipment creation
|
||||
Merchants SHALL create shipments (发货单) for their own orders in `paid` or `fulfilling` status, specifying carrier, tracking_no and per-item quantities.
|
||||
|
||||
#### Scenario: partial shipment
|
||||
- **WHEN** an order has 3 units of item X and a shipment covers 2
|
||||
- **THEN** a later shipment may cover the remaining 1; requesting more than the remainder returns 400
|
||||
|
||||
#### Scenario: status propagation
|
||||
- **WHEN** the first shipment is created for a paid order
|
||||
- **THEN** the order becomes `fulfilling`
|
||||
- **WHEN** all ordered quantities are covered by shipped shipments
|
||||
- **THEN** the order becomes `shipped`
|
||||
|
||||
### Requirement: Delivery confirmation
|
||||
Customers SHALL confirm delivery of their own shipments; when every shipment of an order is delivered the order becomes `completed`.
|
||||
|
||||
#### Scenario: confirm delivered
|
||||
- **WHEN** the customer confirms the only shipment of a shipped order
|
||||
- **THEN** shipment becomes `delivered` and the order `completed`
|
||||
|
||||
Reference in New Issue
Block a user