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.
8.6 KiB
8.6 KiB
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。
更新规则
- 开始实现某 change:状态改
implementing。 - 实现并验证完成(含
cargo test -p vmall-api连跑两次全绿、受影响前端 build、浏览器 smoke):状态改verified。 openspec archive <name> --yes且openspec validate --all --strict全绿:状态改archived,填归档日期。- 实施中发现新的跨 change 依赖:先更新本文件依赖图,再继续。
- 全部
archived后:删除本文件。