fix: 修正零售价架构错误 + 清理旧微信配置 + 归档提案 + 前端接口文档
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m12s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m12s
1. 修正 retail_price 架构:
- 删除 batch-pricing 接口的 pricing_target 字段和 retail_price 分支
(上级只能改下级成本价,不能改零售价)
- 新增 PATCH /api/admin/packages/:id/retail-price 接口
(代理自己改自己的零售价,校验 retail_price >= cost_price)
2. 清理旧微信 YAML 配置(已全部迁移到数据库 tb_wechat_config):
- 删除 config.yaml 中 wechat.official_account 配置节
- 删除 NewOfficialAccountApp() 旧工厂函数
- 清理 personal_customer service 中的死代码(旧登录/绑定微信方法)
- 清理 docker-compose.prod.yml 中旧微信环境变量和证书挂载注释
3. 归档四个已完成提案到 openspec/changes/archive/
4. 新增前端接口变更说明文档(docs/前端接口变更说明.md)
5. 修正归档提案和 specs 中关于 pricing_target 的错误描述
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 废弃旧换卡模型能力
|
||||
|
||||
系统 MUST 废弃 `CardReplacementRecord` 作为主业务能力,原因是其仅覆盖卡换卡且缺少收货信息、物流信息、设备换货与全量迁移能力,无法满足当前换货闭环需求。
|
||||
|
||||
#### Scenario: 新换货流程不再写入旧模型
|
||||
- **WHEN** 执行任意新换货流程(H1~H7、G1~G2)
|
||||
- **THEN** 系统 MUST 仅读写 `ExchangeOrder`,不再创建 `CardReplacementRecord` 新记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 旧表迁移为 legacy 保留查询
|
||||
|
||||
系统 SHALL 将 `tb_card_replacement_record` 改名为 `tb_card_replacement_record_legacy`,仅用于历史查询保留。
|
||||
|
||||
系统 MUST NOT 将 legacy 数据回灌到 `tb_exchange_order`。
|
||||
|
||||
#### Scenario: legacy 数据保留但不参与新流程
|
||||
- **WHEN** 运营查询历史老换卡记录
|
||||
- **THEN** 系统可从 legacy 表读取历史数据,但新换货流程 SHALL 不依赖该表
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 旧代码引用替换
|
||||
|
||||
系统 MUST 将旧换卡引用替换为 `ExchangeOrder`,包括 `iot_card_store.go` 中 `is_replaced` 过滤逻辑。
|
||||
|
||||
#### Scenario: is_replaced 基于新换货单判定
|
||||
- **WHEN** 查询 IoT 卡并使用 `is_replaced=true` 过滤
|
||||
- **THEN** 系统 MUST 基于 `ExchangeOrder` 状态判定是否已发生换货,而非 legacy 表
|
||||
@@ -0,0 +1,24 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 设备换货状态语义扩展
|
||||
|
||||
系统 SHALL 将 `asset_status=3` 定义为“已换货”,用于标记已被换出的旧设备资产。
|
||||
|
||||
#### Scenario: 换货完成后旧设备标记
|
||||
- **WHEN** H5 确认完成且旧资产为设备
|
||||
- **THEN** 系统 MUST 将旧设备 `asset_status` 更新为 `3`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 设备转新重置规则
|
||||
|
||||
系统 SHALL 在 H7 转新时对设备执行以下重置:
|
||||
- `generation = generation + 1`
|
||||
- `asset_status = 1`(在库)
|
||||
- 清空累计充值与首充触发相关状态
|
||||
- 清除个人客户绑定关系
|
||||
- 创建新空钱包并与新代际设备关联
|
||||
|
||||
#### Scenario: 转新后设备可重新销售
|
||||
- **WHEN** 对已换货设备执行转新
|
||||
- **THEN** 系统 MUST 使该设备进入新代际并恢复在库可售
|
||||
@@ -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 拒绝并返回“资产当前状态不允许转新”
|
||||
@@ -0,0 +1,35 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: G1 查询进行中换货通知
|
||||
|
||||
系统 SHALL 提供 `GET /api/c/v1/exchange/pending?identifier=xxx`(需个人客户认证 `Auth=true`)。
|
||||
|
||||
系统 MUST 根据资产标识查询当前客户可见的进行中换货单,仅返回 `status IN (1,2,3)` 的记录。
|
||||
|
||||
响应 SHALL 至少包含:换货单 ID、单号、状态、换货原因、创建时间。
|
||||
|
||||
错误响应 MUST 至少包含:参数错误、资产不存在或无权限。
|
||||
|
||||
#### Scenario: 命中进行中换货单
|
||||
- **WHEN** 客户按资产标识查询且存在状态为 2 的换货单
|
||||
- **THEN** 系统返回该换货单并标识当前状态为待发货
|
||||
|
||||
---
|
||||
|
||||
### Requirement: G2 填写收货信息
|
||||
|
||||
系统 SHALL 提供 `POST /api/c/v1/exchange/:id/shipping-info`(需个人客户认证 `Auth=true`)。
|
||||
|
||||
请求体 MUST 包含:`recipient_name`、`recipient_phone`、`recipient_address`。
|
||||
|
||||
系统 MUST 校验:
|
||||
- 换货单存在且当前客户有权限
|
||||
- 当前状态必须为 `1`
|
||||
|
||||
成功后 SHALL 写入收货信息并将状态更新为 `2`。
|
||||
|
||||
错误响应 MUST 至少包含:参数错误、状态非法、换货单不存在或无权限。
|
||||
|
||||
#### Scenario: 非待填写状态禁止更新收货信息
|
||||
- **WHEN** 换货单当前状态为 `2` 或 `3`
|
||||
- **THEN** 系统 MUST 拒绝填写并返回状态非法错误
|
||||
@@ -0,0 +1,60 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 全量迁移事务边界
|
||||
|
||||
系统 MUST 在 H5 确认完成且 `migrate_data=true` 时,使用**单一数据库事务**执行全量迁移。
|
||||
|
||||
该事务 SHALL 覆盖资产钱包、套餐、标签、客户绑定及资产状态更新等所有步骤;任一步骤失败 MUST 回滚。
|
||||
|
||||
#### Scenario: 迁移中途失败回滚
|
||||
- **WHEN** 迁移第 N 步发生数据库错误
|
||||
- **THEN** 系统 MUST 回滚整个事务,换货单状态保持未完成
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 11 张表迁移规则
|
||||
|
||||
系统 SHALL 按以下规则处理 11 张表:
|
||||
|
||||
1. `tb_asset_wallet`:将旧资产钱包余额转移到新资产钱包。
|
||||
2. `tb_asset_wallet_transaction`:生成一条迁移流水记录(明确来源钱包、目标钱包、金额、业务类型)。
|
||||
3. `tb_asset_recharge_record`:历史充值记录保留,不做更新。
|
||||
4. `tb_package_usage`:将生效套餐关联到新资产(更新 `iot_card_id` 或 `device_id`)。
|
||||
5. `tb_package_usage_daily_record`:随 `tb_package_usage` 关系迁移(保持套餐日明细连续性)。
|
||||
6. `tb_order`:历史订单保留,不做更新。
|
||||
7. `tb_commission`:历史分佣记录保留,不做更新。
|
||||
8. `tb_data_usage_record`:历史流量记录保留,不做更新。
|
||||
9. `tb_resource_tag`:复制旧资产标签到新资产。
|
||||
10. `tb_personal_customer_device`:将绑定记录中的 `virtual_no` 更新为新资产虚拟号。
|
||||
11. `tb_iot_card`/`tb_device`:迁移累计充值与首充状态到新资产,并将旧资产 `asset_status -> 3`。
|
||||
|
||||
#### Scenario: 钱包余额转移并记录流水
|
||||
- **WHEN** 旧资产钱包余额为 5000 分
|
||||
- **THEN** 新资产钱包余额增加 5000 分,旧钱包余额按迁移策略清零,并写入迁移流水
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 设备换设备特殊规则
|
||||
|
||||
设备换设备流程 MUST NOT 迁移 `DeviceSimBinding`。
|
||||
|
||||
系统 SHALL 视新设备为新硬件交付,新设备卡绑定由其自身体系决定,旧设备绑定关系保留历史。
|
||||
|
||||
#### Scenario: 设备换设备不复制绑定卡
|
||||
- **WHEN** 执行设备换设备全量迁移
|
||||
- **THEN** 系统 MUST 不创建或复制任何 `DeviceSimBinding` 记录到新设备
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 转新规则
|
||||
|
||||
系统 SHALL 在 H7 转新时执行代际隔离策略:
|
||||
- 资产 `generation + 1`
|
||||
- 创建新空钱包(新 `wallet_id`)
|
||||
- 清除累计充值状态与首充触发状态
|
||||
- 清除 `PersonalCustomerDevice` 绑定
|
||||
- 不删除历史业务数据
|
||||
|
||||
#### Scenario: 转新后历史数据保留
|
||||
- **WHEN** 资产转新完成
|
||||
- **THEN** 历史订单、充值、分佣、流量数据 MUST 仍可在旧代际查询链路中追溯
|
||||
@@ -0,0 +1,69 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: ExchangeOrder 换货单模型定义
|
||||
|
||||
系统 SHALL 定义 `ExchangeOrder` 模型并映射到 `tb_exchange_order`,用于承载客户端换货完整生命周期。
|
||||
|
||||
模型字段 MUST 至少包含:
|
||||
- 基础:`id`、`created_at`、`updated_at`、`deleted_at`、`creator`、`updater`
|
||||
- 单号:`exchange_no`
|
||||
- 旧资产:`old_asset_type`、`old_asset_id`、`old_asset_identifier`
|
||||
- 新资产:`new_asset_type`、`new_asset_id`、`new_asset_identifier`
|
||||
- 收货:`recipient_name`、`recipient_phone`、`recipient_address`
|
||||
- 物流:`express_company`、`express_no`
|
||||
- 迁移:`migrate_data`、`migration_completed`、`migration_balance`
|
||||
- 业务:`exchange_reason`、`remark`、`status`
|
||||
- 多租户:`shop_id`
|
||||
|
||||
`ExchangeOrder` SHALL 嵌入 `BaseModel` 并实现 `TableName() string`,返回 `tb_exchange_order`。
|
||||
|
||||
#### Scenario: 创建换货单模型实例
|
||||
- **WHEN** 系统创建新的换货单记录
|
||||
- **THEN** 记录 MUST 同时包含旧资产快照、收货信息占位、迁移状态字段和多租户字段
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 换货状态常量定义
|
||||
|
||||
系统 MUST 使用 int 常量定义换货状态:
|
||||
- `1` 待填写信息
|
||||
- `2` 待发货
|
||||
- `3` 已发货待确认
|
||||
- `4` 已完成
|
||||
- `5` 已取消
|
||||
|
||||
#### Scenario: 状态常量一致性
|
||||
- **WHEN** Service、Store、Handler 读取或更新换货状态
|
||||
- **THEN** 各层 MUST 使用统一常量值,禁止硬编码散落魔法数字
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 换货状态机流转规则
|
||||
|
||||
系统 SHALL 执行以下状态机:
|
||||
- 创建换货单后:`1`
|
||||
- 客户填写收货信息后:`1 -> 2`
|
||||
- 后台发货后:`2 -> 3`
|
||||
- 后台确认完成后:`3 -> 4`
|
||||
- 取消:仅允许 `1/2 -> 5`
|
||||
|
||||
系统 MUST 禁止非法流转(如 `3 -> 5`、`4 -> 2`)。
|
||||
|
||||
#### Scenario: 已发货不可取消
|
||||
- **WHEN** 换货单状态为 `3` 且请求取消
|
||||
- **THEN** 系统 MUST 拒绝并返回状态流转非法错误
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 换货单号生成规则
|
||||
|
||||
系统 MUST 为每个换货单生成全局可追踪单号,格式为:`EXC + 时间戳片段 + 随机数片段`。
|
||||
|
||||
生成规则 SHALL 满足:
|
||||
- 前缀固定为 `EXC`
|
||||
- 包含日期/时间信息用于人工排查
|
||||
- 包含随机片段降低并发冲突概率
|
||||
|
||||
#### Scenario: 生成换货单号
|
||||
- **WHEN** 后台发起换货并创建新单
|
||||
- **THEN** 系统 MUST 生成形如 `EXC20260319XXXXXX` 的单号并写入 `exchange_no`
|
||||
@@ -0,0 +1,23 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: IoT 卡换货状态语义扩展
|
||||
|
||||
系统 SHALL 将 `asset_status=3` 定义为“已换货”,用于标记已被换出、不可继续作为当前代际在售资产的 IoT 卡。
|
||||
|
||||
#### Scenario: 换货完成后旧卡标记为已换货
|
||||
- **WHEN** H5 确认完成且旧资产为 IoT 卡
|
||||
- **THEN** 系统 MUST 将旧卡 `asset_status` 更新为 `3`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: IoT 卡转新重置规则
|
||||
|
||||
系统 SHALL 在 H7 转新时对 IoT 卡执行以下重置:
|
||||
- `generation = generation + 1`
|
||||
- `asset_status = 1`(在库)
|
||||
- 清空累计充值与首充触发相关状态(含 `AccumulatedRecharge`、`FirstCommissionPaid`、系列首充/累计字段)
|
||||
- 清除个人客户绑定关系
|
||||
|
||||
#### Scenario: 转新后进入新代际
|
||||
- **WHEN** 对旧卡执行转新
|
||||
- **THEN** 系统 MUST 使该卡进入新代际并以在库状态重新销售
|
||||
@@ -0,0 +1,21 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 换货迁移时更新个人客户资产绑定
|
||||
|
||||
系统 SHALL 在 H5 全量迁移成功后,更新 `PersonalCustomerDevice` 的资产标识绑定关系:
|
||||
- 若旧资产存在客户绑定,绑定中的 `virtual_no` MUST 更新为新资产 `virtual_no`
|
||||
- 更新后客户对资产访问连续,不需重新登录即可看到新资产
|
||||
|
||||
#### Scenario: 迁移后客户绑定跟随新资产
|
||||
- **WHEN** 旧资产存在个人客户绑定且执行了 `migrate_data=true`
|
||||
- **THEN** 系统 MUST 将绑定记录的 `virtual_no` 更新为新资产虚拟号
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 转新时清除个人客户绑定
|
||||
|
||||
系统 SHALL 在 H7 转新时清除该资产在 `PersonalCustomerDevice` 中的绑定关系,避免旧客户继续访问新代际资产。
|
||||
|
||||
#### Scenario: 转新后旧客户需重新绑定
|
||||
- **WHEN** 资产转新完成
|
||||
- **THEN** 系统 MUST 删除或失效对应客户绑定,使旧客户再次访问时触发重新绑定流程
|
||||
Reference in New Issue
Block a user