feat: backend MVP (auth/rbac, catalog, orders, fulfillment, invoices) + specs + scaffolds

This commit is contained in:
Chengdong Zhang
2026-09-17 12:43:22 +08:00
commit dc9fd31c5e
96 changed files with 17550 additions and 0 deletions
@@ -0,0 +1,18 @@
# Proposal: catalog-i18n-currency
## Why
Merchants must manage products (create, edit, publish/unpublish) and shoppers must browse a localized, multi-currency storefront. This phase delivers the catalog core plus the currency infrastructure every price display depends on.
## What changes
- Schema: categories, products (JSONB i18n name/description), skus (price minor units + currency + stock), currencies (rate_to_base).
- Public APIs: product list (paged, filter by category/q/shop), product detail by id/slug, category list, currency list + conversion endpoint.
- Shop-admin APIs: product CRUD, SKU upsert, publish/unpublish transitions.
- Seed: base currency USD, plus CNY/EUR/JPY; demo categories.
## Non-goals
- Inventory reservations/warehouses, product variants matrix UI, image upload (URLs only).
- Full-text search engines; ILIKE search is sufficient for MVP.
## Capabilities
- `catalog`: categories, products, SKUs, publish lifecycle.
- `currency`: currency registry and conversion.
@@ -0,0 +1,37 @@
# Spec delta: catalog
## ADDED 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,21 @@
# Spec delta: currency
## ADDED 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,22 @@
# Tasks: catalog-i18n-currency
## 1. Schema
- [ ] Migration: currencies, categories, products, skus; product status enum
- [ ] Seed currencies (USD base, CNY, EUR, JPY) + demo categories (bilingual)
## 2. Currency APIs
- [ ] GET /api/currencies (public, enabled only)
- [ ] GET /api/currencies/convert?amount_minor&from&to
## 3. Public catalog APIs
- [ ] GET /api/products (paged; filters: category_id, q, shop_id; only published)
- [ ] GET /api/products/{id_or_slug} (published only, with SKUs)
- [ ] GET /api/categories
## 4. Shop-admin catalog APIs
- [ ] GET/POST /api/shop/products, PUT /api/shop/products/{id}
- [ ] POST /api/shop/products/{id}/publish | /unpublish
- [ ] POST /api/shop/products/{id}/skus (upsert by sku_code)
## 5. Tests
- [ ] cargo test: publish lifecycle visibility, i18n fields round-trip, currency conversion math, shop isolation