Files
junhong_cmp_fiber/openspec/changes/client-exchange-system/proposal.md
huang e78f5794b9 feat: 实现客户端换货系统(client-exchange-system)
新增完整换货生命周期管理:后台发起 → 客户端填收货信息 → 后台发货 → 确认完成(含可选全量迁移) → 旧资产转新再销售

后台接口(7个):
- POST /api/admin/exchanges(发起换货)
- GET /api/admin/exchanges(换货列表)
- GET /api/admin/exchanges/:id(换货详情)
- POST /api/admin/exchanges/:id/ship(发货)
- POST /api/admin/exchanges/:id/complete(确认完成+可选迁移)
- POST /api/admin/exchanges/:id/cancel(取消)
- POST /api/admin/exchanges/:id/renew(旧资产转新)

客户端接口(2个):
- GET /api/c/v1/exchange/pending(查询换货通知)
- POST /api/c/v1/exchange/:id/shipping-info(填写收货信息)

核心能力:
- ExchangeOrder 模型与状态机(1待填写→2待发货→3已发货→4已完成,1/2可取消→5)
- 全量迁移事务(11张表:钱包、套餐、标签、客户绑定等)
- 旧资产转新(generation+1、状态重置、新钱包、历史隔离)
- 旧 CardReplacementRecord 表改名为 legacy,is_replaced 过滤改为查新表
- 数据库迁移:000085 新建 tb_exchange_order,000086 旧表改名
2026-03-19 13:26:54 +08:00

7.8 KiB
Raw Blame History

Why

现有 CardReplacementRecord 模型仅支持简单换卡,无法满足完整换货需求:缺少收货地址、快递信息、设备换货、全量数据迁移等功能。客户端换货场景中,后台发起换货 → 客户端收到通知填写收货信息 → 后台发货+确认完成(含全量迁移)→ 旧资产可"转新"重新销售,是一个跨后台/客户端的完整业务闭环。

前置依赖:提案 0asset_status/generation 字段已就位)、提案 1客户端认证

What Changes

新增模型

  • ExchangeOrder换货单:完整的换货生命周期模型,包含旧/新资产信息、收货地址、物流信息、迁移状态。状态机:1-待填写信息 → 2-待发货 → 3-已发货待确认 → 4-已完成1 或 2 时可取消(→5)

删除旧模型

  • CardReplacementRecord:表改名为 tb_card_replacement_record_legacy,代码引用替换为 ExchangeOrder

后台换货管理(模块 H7 个接口)

  • H1 发起换货 POST /api/admin/exchanges:验证资产无进行中换货单,创建 status=1
  • H2 换货列表 GET /api/admin/exchanges:支持状态筛选、资产标识符搜索、时间范围
  • H3 换货详情 GET /api/admin/exchanges/:id
  • H4 发货 POST /api/admin/exchanges/:id/ship:填写物流信息+新资产标识符,验证 status=2、同类型资产、新资产 asset_status=1在库
  • H5 确认完成 POST /api/admin/exchanges/:id/complete:验证 status=3如 migrate_data=true 则执行全量迁移11 张表事务内操作),旧资产 asset_status→3
  • H6 取消换货 POST /api/admin/exchanges/:id/cancel:验证 status IN (1,2),已发货不可取消
  • H7 旧资产转新 POST /api/admin/exchanges/:id/renew:旧资产 asset_status 从 3→1generation+1清除客户绑定和累计状态创建新空钱包

客户端换货(模块 G2 个接口)

  • G1 查询换货通知 GET /api/c/v1/exchange/pending?identifier=xxx:查是否有进行中的换货单
  • G2 填写收货信息 POST /api/c/v1/exchange/:id/shipping-info:验证 status=1更新收货信息status→2

换货状态机

后台发起换货
    │
    ▼
┌─────────────────────┐
│ 1-待填写信息        │ ←── ExchangeOrder 创建
│ (等待客户端填写)     │
└──────────┬──────────┘
           │
    客户端填写收货信息 [G2]
           │
           ▼
┌─────────────────────┐
│ 2-待发货            │
│ (等待后台填写物流)   │
└──────────┬──────────┘
           │
    后台发货 [H4] (填物流+新资产)
           │
           ▼
┌─────────────────────┐
│ 3-已发货待确认      │
│ (等待后台确认完成)   │
└──────────┬──────────┘
           │
    后台确认完成 [H5]
    (可选: 全量迁移)
           │
           ▼
┌─────────────────────┐
│ 4-已完成            │
└─────────────────────┘

取消: status=1 或 2 时可取消 → 5-已取消
已发货(status=3)后不可取消

全量迁移流程H5 确认完成时触发)

确认完成 (migrate_data=true)
    │
    ▼
事务开始
    │
    ├── 1. 钱包余额转移
    │   旧 AssetWallet.Balance → 新 AssetWallet
    │   生成迁移流水 AssetWalletTransaction
    │
    ├── 2. 生效中套餐关联新资产
    │   PackageUsage WHERE iot_card_id/device_id=旧 AND status IN (生效中)
    │   → UPDATE iot_card_id/device_id = 新
    │
    ├── 3. 累计充值/首充状态迁移
    │   IotCard/Device 的 AccumulatedRecharge/FirstCommissionPaid
    │   等字段复制到新资产
    │
    ├── 4. 标签复制
    │   ResourceTag WHERE resource_type=? AND resource_id=旧
    │   → 为新资产创建相同标签
    │
    ├── 5. 个人客户绑定更新
    │   PersonalCustomerDevice WHERE virtual_no=旧虚拟号
    │   → UPDATE virtual_no = 新虚拟号
    │
    ├── 6. 旧资产标记
    │   旧资产 asset_status → 3已换货
    │
    └── 7. 记录迁移信息
        ExchangeOrder: migration_completed=true,
        migration_balance=转移金额
    │
    ▼
事务提交
    │
    ▼
ExchangeOrder status → 4已完成

注意:
- 设备换设备时不迁移 DeviceSimBinding卡绑定关系
- 新设备自带新的 SIM 卡,旧设备的卡绑定保持不变
- 保留不修改的表: tb_order, tb_commission, tb_data_usage_record,
  tb_asset_recharge_record历史记录保留通过 generation 隔离)

转新流程H7

旧资产 (asset_status=3 已换货)
    │
    ▼
POST /api/admin/exchanges/:id/renew
    │
    ├── 1. asset_status: 3 → 1在库
    ├── 2. generation: +1进入新世代
    ├── 3. 清除: 累计充值状态、首充触发状态
    ├── 4. 清除: PersonalCustomerDevice 绑定
    ├── 5. 创建新空钱包(新 wallet_id
    └── 6. 不删除历史数据(通过 generation 隔离)
    │
    ▼
旧资产可重新销售给新客户
新客户查询时按当前 generation 过滤
看不到旧周期数据

Capabilities

New Capabilities

  • exchange-order-modelExchangeOrder 模型定义、状态机、状态常量、换货单号生成规则
  • exchange-admin-management:后台换货管理 CRUDH1~H3、发货H4含同类型资产校验+新资产在库校验、确认完成H5含全量迁移事务、取消H6、转新H7含 generation 自增+状态重置)
  • exchange-data-migration全量迁移逻辑11 张数据表的事务内操作规则,设备不迁移卡绑定的特殊规则
  • exchange-client-notification客户端换货通知查询G1、收货信息填写G2

Modified Capabilities

  • iot-cardIotCard 新增换货相关行为——asset_status=3 标记、转新时 generation 自增+状态重置
  • deviceDevice 同上
  • personal-customerPersonalCustomerDevice 绑定关系在换货迁移时更新虚拟号
  • card-replacementREMOVED — CardReplacementRecord 模型废弃,表改名为 legacy代码引用替换为 ExchangeOrder

Impact

  • 新增文件internal/model/exchange_order.go(模型);internal/handler/admin/exchange.go(后台 Handlerinternal/handler/app/client_exchange.go(客户端 Handlerinternal/service/exchange/service.goService含迁移逻辑internal/store/postgres/exchange_order_store.goStoreDTO 文件;迁移文件;常量和错误码
  • 修改文件internal/model/card_replacement.go(删除或标记废弃);internal/store/postgres/iot_card_store.go(移除 is_replaced 过滤改为查新表);internal/model/system.goAutoMigrate 移除旧模型+注册新模型);internal/routes/(新增后台+客户端路由);internal/bootstrap/(注册新模块);cmd/api/docs.go + cmd/gendocs/main.go(文档生成器)
  • 新增 API 路由:后台 /api/admin/exchanges/ 下 7 个端点 + 客户端 /api/c/v1/exchange/ 下 2 个端点
  • 数据库变更:新建 tb_exchange_order 表;旧表 tb_card_replacement_record 改名为 tb_card_replacement_record_legacy
  • 全量迁移涉及 11 张表tb_asset_wallettb_asset_wallet_transactiontb_asset_recharge_recordtb_package_usagetb_package_usage_daily_recordtb_ordertb_commissiontb_data_usage_recordtb_resource_tagtb_personal_customer_devicetb_iot_card/tb_device