docs(openspec): propose 8 tigshop migration changes (P0-P7) + migration plan

This commit is contained in:
Chengdong Zhang
2026-09-23 13:30:36 +08:00
parent 0d0e10b97b
commit 9a749e2551
47 changed files with 1607 additions and 0 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-23
@@ -0,0 +1,28 @@
## Why
A shop can currently exist only when a platform admin creates it directly and assigns an owner by hand. There is no merchant-facing entry: prospective sellers cannot apply, nothing records their entity and qualification data, and admins have no review queue. As the 2B entry of the B2B2C mall, joining as a merchant must become an application-and-review flow instead of a manual admin back-office step.
## What Changes
- Add a public merchant onboarding application ("商家入驻") with two form kinds — personal (个人) and enterprise (企业) — covering entity information, operating categories, contact details, and qualification materials submitted as URL fields only.
- Require registration or sign-in before submission: the form can be filled anonymously, but only an authenticated user can submit, and a user holding a `pending` or `approved` application is deduplicated and rejected.
- Add a review state machine `pending -> approved | rejected`: platform admins review applications in a queue, rejection requires a reason, and approval creates the shop and a dedicated `shop_owner` account in the same transaction, issuing initial credentials displayed exactly once.
- Add a mall application status page so an applicant can track their own review state and rejection reason, with re-apply after rejection.
- Land the mall top-bar "商家入驻" entry on the onboarding page instead of the stores directory.
## Capabilities
### New Capabilities
- `merchant-onboarding`: Application submission with per-user deduplication, the review state machine, transactional approval provisioning with one-time credentials, and applicant/admin visibility.
### Modified Capabilities
- `frontend-mall`: Multi-step merchant onboarding form and application status page behind the top-bar "商家入驻" entry.
- `frontend-admin`: Merchant application list and review console with approve/reject actions.
## Non-goals
File upload storage, qualification OCR and real-name identity verification, onboarding fees, public notice (公示期) periods, supplier (non-shop) onboarding, and multiple active applications per user are excluded.
## Impact
Adds one Postgres migration (`merchant_applications`), a Rust merchant-onboarding module with customer and admin routes, shared types and API methods, a `merchant-onboarding` live-domain pick with fixed-adapter parity, Mall onboarding and status pages, and an admin review console. Approval reuses the existing identity and shop provisioning paths inside one transaction.
@@ -0,0 +1,20 @@
## ADDED Requirements
### Requirement: Merchant application review console
Platform admins SHALL review merchant onboarding applications in a dedicated console entry beside existing platform operations: a status-filtered paginated list and a detail view showing entity kind, entity information, operating categories, contact details, and qualification URL fields. Approving SHALL provision the shop and `shop_owner` account and display the one-time initial credentials exactly once, with copy stating the password cannot be retrieved again. Rejecting SHALL require a reason. Reviewed applications SHALL leave the pending queue immediately.
#### Scenario: review an enterprise application
- **WHEN** a platform admin opens a pending enterprise application
- **THEN** the company entity data, operating categories, contact details, and qualification URLs are visible for review
#### Scenario: approve shows one-time credentials
- **WHEN** a platform admin approves an application
- **THEN** the created shop owner's initial credentials are shown once and the application moves to the approved list
#### Scenario: reject requires a reason
- **WHEN** a platform admin attempts to reject without entering a reason
- **THEN** the action is blocked until a non-empty reason is provided
#### Scenario: entry beside platform operations
- **WHEN** an authenticated platform admin opens the admin console
- **THEN** merchant application review is reachable from the console navigation
@@ -0,0 +1,31 @@
## ADDED Requirements
### Requirement: Merchant onboarding multi-step form
The mall SHALL expose a merchant onboarding page ("商家入驻") reachable from the top-bar entry, with a multi-step form covering entity kind (personal 个人 / enterprise 企业), kind-specific entity information, operating categories from the published category tree, contact details, and qualification materials as URL input fields with no file-upload controls. The form SHALL be fillable while signed out, but submission SHALL require registration or sign-in and return the applicant to the completed form to submit through the shared selected API adapter. A duplicate-application conflict SHALL be surfaced inline. All copy SHALL come from the mall locale source in en and zh.
#### Scenario: anonymous fill then sign-in
- **WHEN** a signed-out visitor completes the form and submits
- **THEN** they are sent to register or sign in and, once signed in, returned to the completed form to submit
#### Scenario: enterprise kind shows company fields
- **WHEN** the applicant selects the enterprise entity kind
- **THEN** the company-specific entity and qualification fields replace the personal ones
#### Scenario: duplicate application surfaced
- **WHEN** a signed-in user holding a pending or approved application submits the form
- **THEN** the mall shows the conflict instead of silently creating a second application
#### Scenario: fixed adapter remains functional
- **WHEN** the merchant-onboarding domain is configured to fixed data
- **THEN** the form and status flows behave deterministically through the same shared client methods
### Requirement: Application status page
The mall SHALL show a signed-in applicant their latest merchant application state — status, submitted entity kind, timestamps, and the rejection reason when rejected — linked from the onboarding page, with a re-apply action after rejection. Anonymous visitors SHALL be sent to sign in first.
#### Scenario: applicant tracks review
- **WHEN** a signed-in applicant opens the status page while the application is `pending`
- **THEN** the pending state and submission summary are shown
#### Scenario: rejection explains reason
- **WHEN** a rejected applicant opens the status page
- **THEN** the rejection reason is displayed with an action to apply again
@@ -0,0 +1,68 @@
## ADDED Requirements
### Requirement: Merchant application submission
A prospective seller SHALL submit one merchant onboarding application as either a personal (个人) or an enterprise (企业) entity. Each kind SHALL require its own entity information, one or more operating categories referenced from the published category tree, contact details, and qualification materials submitted as URL fields. The API MUST reject submissions with missing kind-specific fields, unknown categories, or malformed qualification URLs. The form MAY be filled anonymously, but submission SHALL require an authenticated user.
#### Scenario: enterprise submission accepted
- **WHEN** an authenticated user submits an enterprise application with entity information, operating categories, contact details, and qualification URLs
- **THEN** the application is stored with status `pending` and returned to the applicant
#### Scenario: personal submission missing identity document
- **WHEN** a user submits a personal application without the required identity document URL
- **THEN** the API returns a validation error and stores no application row
#### Scenario: anonymous submit requires sign-in
- **WHEN** a signed-out visitor submits the completed form
- **THEN** no application is stored and the visitor must register or sign in before submitting
### Requirement: One active application per user
Submission SHALL be deduplicated per user: a user holding a `pending` or `approved` application MUST NOT create another one, enforced by service validation backed by a database partial unique index so concurrent submissions cannot both succeed. A user whose application was `rejected` MAY apply again.
#### Scenario: duplicate pending submission
- **WHEN** a user with a `pending` application submits again
- **THEN** the API rejects the request with a conflict and exactly one application row exists
#### Scenario: re-apply after rejection
- **WHEN** a user whose application was `rejected` submits a new application
- **THEN** a new `pending` application row is created
### Requirement: Review state machine
An application SHALL move only from `pending` to `approved` or `rejected`, executed as guarded updates matching the `pending` state so concurrent or repeated reviews of the same application fail with a conflict instead of overwriting each other. Rejecting SHALL require a non-empty reason recorded on the row, and `approved` and `rejected` are terminal states that MUST NOT transition again.
#### Scenario: double review conflict
- **WHEN** two platform admins review the same `pending` application concurrently
- **THEN** exactly one transition succeeds and the other receives a conflict
#### Scenario: rejection records a reason
- **WHEN** a platform admin rejects a `pending` application with a reason
- **THEN** the application becomes `rejected` with the reason stored on the row
#### Scenario: terminal state is immutable
- **WHEN** a platform admin reviews an already `approved` or `rejected` application
- **THEN** the API returns a conflict and the stored state is unchanged
### Requirement: Transactional approval provisioning
Approving an application SHALL, within a single database transaction, create the shop, create a dedicated `shop_owner` account scoped to that shop, and mark the application `approved` referencing the created shop. The generated initial password SHALL be returned exactly once in the approve response and MUST NOT be retrievable through any later read. If any step fails, the status change and all provisioning SHALL roll back so the application remains `pending`.
#### Scenario: approval provisions shop and account
- **WHEN** a platform admin approves a `pending` application
- **THEN** one transaction results in an `approved` application, an active shop, and a working `shop_owner` login scoped to that shop
#### Scenario: credentials shown once
- **WHEN** the approver reads the application again after approval
- **THEN** the initial password is absent from every subsequent response
#### Scenario: provisioning failure rolls back
- **WHEN** shop or account creation fails during approval
- **THEN** no shop or account persists and the application remains `pending`
### Requirement: Application visibility
An applicant SHALL read only their own application history, including status, submitted data, timestamps, and rejection reason. Platform admins SHALL list all applications with status filtering and pagination and read any application detail. Other users' applications MUST NOT be readable through customer routes.
#### Scenario: applicant checks status
- **WHEN** a signed-in applicant requests their application status
- **THEN** their own application state and rejection reason are returned
#### Scenario: other applicant is hidden
- **WHEN** one authenticated user requests another user's application through customer routes
- **THEN** the API returns no data about the other user's application
@@ -0,0 +1,33 @@
## 1. Persistence and shared contract
- [ ] 1.1 Add migration `0017_merchant_applications.sql` (take the next free number if a sibling change claims it): `merchant_applications` with applicant `user_id` FK, `entity_type` enum (`personal`, `enterprise`), kind-specific entity and contact columns, `category_ids`, qualification URL columns (identity document, business license, extra materials as a JSONB URL array), `status` enum (`pending`, `approved`, `rejected`), `rejection_reason`, `reviewed_by`/`reviewed_at`, `created_shop_id` FK, and a partial unique index on `user_id` `WHERE status IN ('pending', 'approved')`.
- [ ] 1.2 Add shared discriminated application types (submit payloads for both entity kinds, application summary/detail with status and rejection reason, one-time approval credentials) and the `submitMerchantApplication`, `getMyMerchantApplications`, `adminListMerchantApplications`, `adminGetMerchantApplication`, `adminApproveMerchantApplication`, and `adminRejectMerchantApplication` methods to `@vmall/shared`.
## 2. Backend service and behavioral tests
- [ ] 2.1 Implement `apps/api/src/modules/merchant_onboarding/` repository, service, DTO, handlers, and module registration: authenticated customer routes for submit and self status, and `platform_admin`-guarded admin routes for list, detail, approve, and reject. Services return `ApiResult<Dto>`.
- [ ] 2.2 Enforce submission validation (kind-specific required fields, published category references, qualification URL shape) and duplicate rejection for users holding a `pending` or `approved` application, with the partial unique index as the concurrency backstop.
- [ ] 2.3 Implement guarded status transitions (`UPDATE ... WHERE status = 'pending'`) with mandatory rejection reason and a 409 conflict for already-reviewed applications.
- [ ] 2.4 Implement approval as a single transaction: create the shop, create a dedicated `shop_owner` account scoped to it, flip the application to `approved` with `created_shop_id`, and return the generated initial password exactly once; any failure rolls back the status flip and all provisioning.
- [ ] 2.5 Add isolated API integration coverage in `apps/api/tests/merchant_applications.rs` (reuse the `tests/common/mod.rs` fixtures) for duplicate submission, double-review conflicts, terminal-state immutability, rejection-reason enforcement, and provisioning rollback.
## 3. Mall onboarding form
- [ ] 3.1 Implement the six merchant-onboarding client methods in `apps/mall/mock/api.ts` with deterministic per-session fixture state mirroring dedupe conflicts, review status, and approval/rejection outcomes.
- [ ] 3.2 Add the `merchant-onboarding` domain and exact shared-client method picks to Mall API selection (`apps/mall/plugins/api.ts` LIVE_PICKS) and enable it in the default live runtime configuration.
- [ ] 3.3 Build the multi-step onboarding page (`/merchant/join`): entity-kind step (personal 个人 / enterprise 企业) switching kind-specific fields, entity information, operating categories from the published category tree, contact details, and qualification URL input fields with no upload controls; allow anonymous filling but gate submission behind registration/sign-in with return to the completed form.
- [ ] 3.4 Build the application status page (`/merchant/status`) showing the applicant's latest application state, submitted entity kind, timestamps, and rejection reason, with a re-apply action after rejection.
- [ ] 3.5 Land the top-bar "商家入驻" (`sellerJoin`) entry and the footer "Become a Seller" link on the onboarding page instead of the stores directory, and add bilingual onboarding, form, and status strings through the existing Mall locale source without per-page hard-coded copy.
## 4. Admin review console
- [ ] 4.1 Add a platform-admin merchant applications page in `apps/admin` with a status-filtered paginated table and a detail view of entity kind, entity information, operating categories, contact details, and qualification URL fields through the shared contract.
- [ ] 4.2 Add approve/reject actions: rejection requires a non-empty reason, approve shows a one-time initial-credentials dialog with explicit copy that the password cannot be retrieved again, and reviewed rows leave the pending queue immediately.
- [ ] 4.3 Register the merchant applications entry in admin navigation beside existing platform operations and add bilingual console strings through the shared locale source.
## 5. Verification and tracker cleanup
- [ ] 5.1 Add end-to-end API integration coverage in `apps/api/tests/merchant_applications.rs` (reuse the `tests/common/mod.rs` fixtures) for submit -> approve provisioning (the created `shop_owner` can log in and manage the linked shop) and submit -> reject -> re-apply, then run the focused merchant onboarding integration tests.
- [ ] 5.2 Run the API plus Mall and browser-smoke: anonymous fill -> sign-in gate -> submit, duplicate submission conflict, mall status page after approve and after reject, admin queue filtering and detail, approve one-time credentials display, and reject-reason enforcement.
- [ ] 5.3 Build all three frontends because the shared API contract changes: `pnpm --filter @vmall/mall build`, `pnpm --filter @vmall/shop-admin build`, and `pnpm --filter @vmall/admin build`.
- [ ] 5.4 Update the README mock boundary for the merchant-onboarding adapter fallback, record any remaining fixture-driven onboarding surfaces in `docs/TBD-marketing.md`, check every OpenSpec task, and run `openspec change validate add-merchant-onboarding --strict` plus `openspec validate --all --strict`.