Files
vmall/openspec/MIGRATION-PLAN.md
T
james 9904696e76 feat: wave 2 migration (P3, P5, P7 openspec changes)
Implements, verifies, and archives the three remaining Wave 2 changes from
openspec/MIGRATION-PLAN.md.

- add-wallet-settlement (P3): demo recharge, guarded withdrawal freeze and
  one-time admin review, paginated own fund entries, idempotent per-shop
  weekly/monthly settlement statements with commission rate and one-time
  payout confirmation.
- add-merchant-onboarding (P5): personal/enterprise applications with one live
  application per user, guarded review with mandatory rejection reason, and
  transactional shop + owner provisioning returning one-time credentials;
  mall onboarding/status pages and an admin review console.
- add-membership-messaging (P7): platform member levels, append-only growth
  accrual on order completion with guarded one-way leveling, order/shipment/
  refund system messages with unread/read state and soft deletion, plus the
  mall header unread badge.

Backend: migrations 0019-0023, new wallet, settlement, merchant_onboarding,
membership and messaging modules, event hooks in order/fulfillment/aftersale,
and integration suites for each. Shared contract extended and all three
frontends updated; code indexes, domain docs, backend guidelines and the
migration tracker synced.

Verification: cargo test -p vmall-api green twice consecutively; mall, admin
and shop-admin builds pass; browser smoke on every new surface; openspec
validate --all --strict green (33 passed).

The three changes share the @vmall/shared contract, the mall mock adapter and
per-app locale/nav files, so they are committed together to keep every commit
buildable.
2026-09-25 15:25:29 +00:00

91 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tigshop → VMall Migration Plan
> 临时跟踪文档:每实现并验证一个 change(tasks 全勾、`openspec archive` 完成、`openspec validate --all --strict` 全绿)后,更新本文档状态表。**8 个 change 全部归档后删除本文件。**
来源:对 `/Users/chengdzhang/github/jamyun/tigshop`(Admin + Uniapp + PHP 后端)的只读盘点。迁移的是领域模型与信息架构,不是代码。
## 明确排除(不迁移)
- 微信小程序专属能力(wx.login / requestPayment / 订阅消息 / 分享 / 企业客服)
- 供应商(vendor)三层体系 —— 架构级决策,非功能迁移
- 分销(tigshop 自身半成品)、完整拖拽装修器、电子卡密/虚拟商品、直播、打印、内置 IM
## 交接说明(handoff)
**进度**:Wave 1 完成(P6/P0/P2/P1 已归档)+ P3 + P5 + P7 完成。只剩 P4(Wave 3)。
**下一个建议**:P4 `add-mobile-h5`——P0/P3 软依赖已全部归档,可直接起步;这也是迁移计划的最后一个 change。
**工作流约定(用户明确要求)**:一次只实现一个 change,完成后停下来等人工 review;可以提议下一个,但不要自动连续实施,除非明确要求连续执行。
**实施要点**:
- 下一个可用迁移号:`0024`(0019 wallet / 0020 settlement / 0021 merchant_applications / 0022 storefront seller link / 0023 membership+messaging 已占用)。各 proposal tasks.md 里的迁移号是提议时抢占的(P4 写 0016/0017),**实施时以实际顺序重编号**。
- 文档与索引已就位:改代码前先查 `docs/code_index/index.md`;`docs/backend-guidelines.md` 有并发/状态机模式与 PG 陷阱;`docs/design-guidelines.md` 管 UI;增删文件要同步索引。
- 前端派发模式已验证:我(主线)做迁移+共享契约+Rust 模块+集成测试,然后按 app 并行派子任务写页面(P3 三个 app 并行;P5/P7 只有 mall+admin,shop-admin 无改动但要求照常构建),主线统一构建+smoke+归档。
- 共享契约的 admin 方法放在 `api.admin.*` 下(去掉任务描述里的 `admin` 前缀),与既有命名一致;mall 的 `LIVE_PICKS` 只需挑客户侧方法。
- 事件型副作用(消息、成长值)要与业务状态迁移放在**同一个事务**里,并靠部分唯一索引 + `ON CONFLICT DO NOTHING` 保证可重试;为此常需把原先只收 `&PgPool` 的 repo 辅助函数改成 `&mut PgConnection`(P7 改了 `maybe_mark_order_shipped` / `maybe_mark_order_completed`,后者返回 `bool` 表示本次是否真的完成,供调用方决定是否计提)。
**踩过的坑(别再踩)**:
- `SUM(bigint)` 在 Postgres 返回 NUMERIC,解码 i64 前必须 `COALESCE(SUM(x),0)::bigint`(售后曾因此 500)。
- sqlx 运行时解码:`cargo check` 绿不代表 SQL 对——加列要同步所有列清单常量与 INSERT 的列数/参数数;只被单模块读写的列(如 `orders.completed_at`)可不进 `models.rs` 行结构,避免牵动所有列清单。
- **新增迁移文件后必须强制重编译**:`sqlx::migrate!` 在编译期把迁移列表嵌进二进制,仅新增 `.sql` 文件时 cargo 不一定重编,`cargo run` 会用旧列表“静默跳过”新迁移(P5 的 0022 就这样没生效)。改完跑 `touch apps/api/src/main.rs` 再起服务,并到 `_sqlx_migrations` 确认真应用了。
- 外币订单退款:客户无该币种账户会失败,用 `account::service::ensure_monetary_account` 懒建(已存在)。
- 共享契约改动要同时改 interface 和 createApi 实现两处;mall 还要 mock 实现 + LIVE_PICKS 精确挑方法(漏挑会静默落回 mock)。createApi/mock 里已有一批相对 ApiClient 的“多余键”(points/brands/shop profile),`nuxt build` 不做类型检查所以不报——别顺手去改共享契约清理。
- `orders.updated_at` 不能当“完成时间”:退款会 bump 它;按周期归集订单必须用专用列(P3 加了 `completed_at`,在完成迁移的那条 UPDATE 里写入)。
- 结算类周期归集:金额跨币种要经 `money::convert_minor` 折回基准币,别直接相加不同币种的最小单位。成长值同理:折回基准币后按基准币 exponent 截断成“整数单位”,再用整数除/乘,别用浮点。
- 会员等级阈值是**全平台唯一**:共享测试库里 `member_levels` 会跨用例/跨轮次残留,凡是断言“最高等级/下一等级/没有等级”的用例都必须先清空该表(P7 的 `reset_levels`)或用“恰好等于自身阈值”的构造,否则随机碰撞导致 flaky。
- 等级显示要**按当前阈值重新推导**,`users.level` 只作为“客户持有该等级”的标记(删除在用等级要据此拒绝);升级只允许单向(guarded update 比较阈值)。
- “一用户一条有效记录”用**部分唯一索引**(`UNIQUE (user_id) WHERE status IN (...)`)+ service 先查一次给友好 409;事务内先 `FOR UPDATE` 锁行、再置备(建店铺/建账号)、最后做 guarded 状态翻转,任何一步失败整笔回滚。
- 邮箱唯一性:为入驻审批新建店主账号时,申请人的联系邮箱可能已被注册;先查重、退化成 `local+owner@domain`,别直接 INSERT 撞唯一键。
- mall 登录页的注册链接必须带上 `redirect`,否则“注册后回到已填表单”不成立(P5 修了 login→register 的 redirect 透传与 register 的跳转);表单要 `novalidate`,否则原生校验会拦掉 submit 事件、本地化内联报错永远不触发。
- `openspec archive` 会写 `## Purpose` 占位(`TBD - created by archiving ...`),`openspec validate --all --strict` 会因此判 fail;归档后必须立刻把新 spec 的 Purpose 写成真句子(Wave 1 的 shipping/aftersale/reviews 也遗留了同样问题,P3 一并修掉)。
- dev server:`pnpm build` 后要 `rm -rf apps/<app>/.nuxt` 再 dev;dev 绑 IPv6 localhost;页面卡 Loading 无报错=旧 tab 拿旧 chunk hash,重启+开新 tab。
- mall 页面用 `playwright-cli` 做登录时别用 `input` 的全局下标取字段(页头搜索框会先匹配),用 placeholder 精确定位;同理别用 `main form input`(VField 不一定包在 form 里),用组件提供的 `data-testid`。
- 测试库迁移校验和冲突(改了已应用的本地迁移):`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`(vmall_test)。
## 依赖图
```
P6 content-admin-ui ──┐(无依赖,热身)
P1 product-reviews ───┤(无依赖,可并行)
P5 onboarding ────────┤(无依赖,可并行)
P7 membership ────────┤(无依赖,可并行)
│
P0 aftersale ─────────┤
│ │
├─→ P3 wallet-settlement(结算需扣减退款,硬依赖 P0 的 aftersales 表)
│ │
└────────┴─→ P4 mobile-h5(售后/钱包页面消费 P0/P3 API;核心链路只依赖现有 API,可与 P3 并行起步)
P2 freight ───────────┘(改 checkout/order totals,与 P0 同触 order 模块,建议与 P0 串行)
```
## 推荐实施波次
| 波次 | Changes | 说明 |
|---|---|---|
| Wave 1 | P6 → P0 → P2 → P1 | P6 热身(纯前端);P0/P2 都动 order 模块故串行;P1 任意位置插入 |
| Wave 2 | P3、P5、P7 | P3 必须在 P0 归档后启动;P5/P7 随时可插 |
| Wave 3 | P4 | 移动 H5 收尾,消费前面所有 API;P0/P3 未完成时对应页面降级 Coming soon |
## 状态跟踪
| # | Change | 依赖 | 状态 | 归档日期 |
|---|---|---|---|---|
| P0 | `add-aftersale-refunds` | — | archived | 2026-09-23 |
| P1 | `add-product-reviews` | — | archived | 2026-09-24 |
| P2 | `add-freight-templates` | —(与 P0 串行) | archived | 2026-09-24 |
| P3 | `add-wallet-settlement` | P0 | archived | 2026-09-24 |
| P4 | `add-mobile-h5` | P0、P3(软) | proposed | — |
| P5 | `add-merchant-onboarding` | — | archived | 2026-09-24 |
| P6 | `add-content-admin-ui` | — | archived | 2026-09-23 |
| P7 | `add-membership-messaging` | — | archived | 2026-09-25 |
状态取值:`proposed` → `implementing` → `verified`(tasks 全勾 + 测试/构建/smoke 通过)→ `archived`。
## 更新规则
1. 开始实现某 change:状态改 `implementing`。
2. 实现并验证完成(含 `cargo test -p vmall-api` 连跑两次全绿、受影响前端 build、浏览器 smoke):状态改 `verified`。
3. `openspec archive <name> --yes` 且 `openspec validate --all --strict` 全绿:状态改 `archived`,填归档日期。
4. 实施中发现新的跨 change 依赖:先更新本文件依赖图,再继续。
5. 全部 `archived` 后:删除本文件。