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 旧表改名
This commit is contained in:
2026-03-19 13:26:54 +08:00
parent df76e33105
commit e78f5794b9
41 changed files with 5242 additions and 10 deletions

View File

@@ -0,0 +1,121 @@
## ADDED Requirements
### Requirement: H1 发起换货单
系统 SHALL 提供 `POST /api/admin/exchanges`(需后台认证 `Auth=true`),用于发起换货单。
请求体 MUST 包含:`old_asset_type``old_identifier``exchange_reason`,可选 `remark`
系统 MUST 校验:
- 旧资产存在且当前用户有权限
- 同一资产不存在进行中的换货单(`status IN (1,2,3)`
成功响应 SHALL 返回新建换货单信息(含 `id``exchange_no``status=1`)。
错误响应 MUST 至少包含:参数错误、资产不存在或无权限、存在进行中换货单。
#### Scenario: 资产已有进行中换货单
- **WHEN** 后台为同一资产重复发起换货
- **THEN** 系统 MUST 拒绝创建并返回“存在进行中的换货单”
---
### Requirement: H2 换货单列表
系统 SHALL 提供 `GET /api/admin/exchanges``Auth=true`),支持分页与条件查询。
查询条件 SHOULD 支持:`status``identifier`(资产标识搜索)、`created_at_start``created_at_end`、分页参数。
响应 SHALL 返回列表与分页元数据。
#### Scenario: 按状态查询待发货单
- **WHEN** 运营查询 `status=2`
- **THEN** 系统返回所有待发货换货单并按创建时间倒序
---
### Requirement: H3 换货单详情
系统 SHALL 提供 `GET /api/admin/exchanges/:id``Auth=true`)查询换货单详情。
响应 MUST 返回旧/新资产信息、收货信息、物流信息、迁移状态信息。
错误响应 MUST 至少包含:换货单不存在或无权限。
#### Scenario: 查询不存在换货单
- **WHEN** 查询不存在的换货单 ID
- **THEN** 系统 MUST 返回“资源不存在或无权限”
---
### Requirement: H4 发货
系统 SHALL 提供 `POST /api/admin/exchanges/:id/ship``Auth=true`)。
请求体 MUST 包含:`express_company``express_no``new_identifier``migrate_data`
系统 MUST 校验:
- 当前状态必须为 `2`
- 新旧资产类型必须一致(卡换卡/设备换设备)
- 新资产必须 `asset_status=1`(在库)
成功后 SHALL 更新新资产信息、物流信息并将状态改为 `3`
错误响应 MUST 至少包含:非法状态、资产类型不匹配、新资产非在库、资产不存在或无权限。
#### Scenario: 新资产类型不一致
- **WHEN** 旧资产为 iot_card 且新资产为 device
- **THEN** 系统 MUST 拒绝发货并返回“换货资产类型必须一致”
---
### Requirement: H5 确认完成
系统 SHALL 提供 `POST /api/admin/exchanges/:id/complete``Auth=true`)。
系统 MUST 校验当前状态为 `3`。当 `migrate_data=true` 时,系统 MUST 执行全量迁移事务(见 `exchange-data-migration` 能力)。
成功后 SHALL
- `migration_completed=true`(若执行迁移)
- 换货单状态更新为 `4`
错误响应 MUST 至少包含:非法状态、迁移失败、换货单不存在或无权限。
#### Scenario: 需要迁移并完成
- **WHEN** 状态为 `3``migrate_data=true`
- **THEN** 系统 MUST 在事务成功后将状态变为 `4` 并记录迁移结果
---
### Requirement: H6 取消换货
系统 SHALL 提供 `POST /api/admin/exchanges/:id/cancel``Auth=true`)。
系统 MUST 仅允许在 `status IN (1,2)` 时取消,成功后状态更新为 `5`
系统 MUST 禁止已发货单取消(`status=3`)。
#### Scenario: 已发货单取消失败
- **WHEN** 换货单状态为 `3` 发起取消
- **THEN** 系统 MUST 返回状态非法错误
---
### Requirement: H7 旧资产转新
系统 SHALL 提供 `POST /api/admin/exchanges/:id/renew``Auth=true`)。
系统 MUST 校验旧资产当前 `asset_status=3`(已换货),并执行:
- `generation + 1`
- `asset_status -> 1`
- 清除累计充值/首充相关状态
- 清除个人客户绑定
- 创建新空钱包
系统 MUST 保留历史数据,不执行历史删除。
错误响应 MUST 至少包含:资产状态不满足转新条件、换货单不存在或无权限。
#### Scenario: 旧资产未处于已换货状态
- **WHEN** 旧资产 `asset_status != 3` 发起转新
- **THEN** 系统 MUST 拒绝并返回“资产当前状态不允许转新”