## 1. 数据模型与迁移 - [x] 1.1 为 `tb_exchange_order` 设计并创建迁移文件,新增 `flow_type`、`shipped_at`、`completed_at` 字段,并补充与新查询语义匹配的索引与默认值。 - [x] 1.2 更新 `internal/model/exchange_order.go`,让模型字段、中文注释、表注释和数据库迁移保持一致。 - [x] 1.3 更新换货相关常量与状态文本映射,新增流程类型常量,禁止在 Service/Handler 中硬编码 `shipping`、`direct`、状态值和资产状态值。 - [x] 1.4 更新 `internal/model/dto/exchange_dto.go`,为后台创建、发货、详情、列表、客户端待处理等 DTO 增加 `flow_type`、`shipped_at`、`completed_at` 以及 `direct` 所需入参约束。 ## 2. 后台接口契约调整 - [x] 2.1 改造 `POST /api/admin/exchanges` 的 DTO、Handler、Service 入参校验,使 `flow_type=shipping` 与 `flow_type=direct` 的必填参数和错误语义明确分支。 - [x] 2.2 改造 `GET /api/admin/exchanges` 与 `GET /api/admin/exchanges/:id` 的响应结构,补充 `flow_type`、`shipped_at`、`completed_at`,确保后台能区分 `shipping` 与 `direct` 已完成单据。 - [x] 2.3 限定 `POST /api/admin/exchanges/:id/ship` 仅适用于 `shipping` 流程,并在响应与错误语义中体现“不适用于 direct 单据”。 - [x] 2.4 改造 `POST /api/admin/exchanges/:id/complete`,让其仅处理 `shipping` 且 `status=3` 的单据,并显式复用统一完成事务。 - [x] 2.5 保持 `POST /api/admin/exchanges/:id/cancel` 仅对 `shipping` 的 `status=1/2` 生效,明确 `direct` 单据不进入取消分支。 - [x] 2.6 明确创建接口兼容策略:未传 `flow_type` 按 `shipping` 处理,非法 `flow_type` 返回参数错误;`direct` 未传 `migrate_data` 按 `false` 处理。 ## 3. 完成换货事务重构 - [x] 3.1 在 `internal/service/exchange/service.go` 中提炼统一的“完成换货”内部事务方法,覆盖旧资产状态切换、新资产状态切换、绑定切换、完成时间写入和单据状态更新。 - [x] 3.2 重构 `internal/service/exchange/migration.go`,将现有自开事务的迁移逻辑改造成可复用的事务内步骤,由外层事务统一提交或回滚。 - [x] 3.3 在完成换货事务中拆分“必做切换”和“可选迁移”,确保 `migrate_data=false` 时仍然会执行旧资产 `asset_status -> 3`、新资产 `asset_status -> 2`、绑定切换和单据完成。 - [x] 3.4 为完成换货事务增加客户绑定承接校验:若旧资产存在 `PersonalCustomerDevice` 绑定,而新资产无法承接 `virtual_no`,则整单失败回滚。 - [x] 3.5 在完成换货事务中补充新资产有效性校验,至少覆盖资产存在、类型一致、在库状态、未被脏绑定占用等前置条件。 - [x] 3.6 在完成换货事务中补充旧资产 `asset_status=2`、新旧资产 `shop_id` 一致、新资产未被其他换货单占用、旧钱包无冻结余额等边界校验。 ## 4. `direct` 流程建单即完成 - [x] 4.1 在换货创建服务中新增 `flow_type=direct` 分支,使后台创建时即可携带 `new_identifier`、`migrate_data` 并直接进入完成事务。 - [x] 4.2 确保 `direct` 流程对 `iot_card` 与 `device` 都按相同规则校验新旧资产类型一致、库存状态正确、绑定可承接。 - [x] 4.3 保证 `direct` 创建成功后返回的换货单已经具备 `status=4`、`completed_at`、新资产快照和可选迁移结果,不产生半成品处理中单据。 - [x] 4.4 补充 `direct` 流程的中文错误语义,覆盖缺少 `new_identifier`、新资产不存在、类型不一致、绑定不可承接、迁移失败等关键场景。 - [x] 4.5 保证 `direct` 创建即完成使用单一事务,任一步失败时整单不落库,不产生 `flow_type=direct AND status IN (1,2,3)` 的记录。 ## 5. `shipping` 流程兼容改造 - [x] 5.1 保持 `shipping` 创建仍然默认创建 `status=1` 单据,并将 `flow_type` 明确写为 `shipping`。 - [x] 5.2 让客户端填写收货地址仍然只处理 `shipping + status=1`,并保证状态从 `1 -> 2` 的条件更新语义不变。 - [x] 5.3 改造后台发货逻辑,使其在 `shipping + status=2` 时写入 `new_*`、`express_*`、`migrate_data`、`shipped_at` 并推进到 `status=3`。 - [x] 5.4 改造后台完成逻辑,使其在 `shipping + status=3` 时复用统一完成事务,而不是继续沿用“迁移成功后再单独更新单据状态”的旧实现。 - [x] 5.5 为 `shipping` 发货后的新资产占用增加校验或约束,避免同一新资产被多个进行中换货单占用。 ## 6. 个人客户绑定与资产生命周期 - [x] 6.1 改造换货完成时的 `PersonalCustomerDevice` 处理逻辑,将“绑定切换到新资产”从可选迁移提升为完成换货必做动作。 - [x] 6.2 保持 `renew` 仅在旧资产已换货且换货单已完成时执行,并明确 `generation + 1`、重新入库、清理绑定、重建空钱包的事务边界。 - [x] 6.3 更新 `IotCard` 与 `Device` 在换货换出、换入、转新三类场景下的状态更新逻辑,确保旧资产进入 `3 已换货`、新资产进入 `2 已销售`、转新后旧资产回到 `1 在库`。 - [x] 6.4 确保 `generation` 只在 `renew` 流程中递增,不在 `shipping/direct` 完成换货阶段发生变化。 - [x] 6.5 统一个人客户绑定键规则:IoT 卡使用 `virtual_no`,设备优先 `virtual_no`、为空时使用 `imei`,并禁止新资产存在有效绑定冲突。 ## 7. 查询、追溯与文档生成 - [x] 7.1 改造客户端待处理查询,使 `GET /api/c/v1/exchange/pending` 仅返回 `shipping` 且仍处于处理中状态的单据,`direct` 单据不进入待处理视图。 - [x] 7.2 改造资产历史订单追溯逻辑,使 `include_previous=true` 同时覆盖 `shipping` 与 `direct` 的已完成换货链,并优先使用 `completed_at` 作为 `exchanged_at`。 - [x] 7.3 改造换货单列表、详情和追溯链展示字段,保证后台与追溯接口都能看出流程类型、发货时间、完成时间和来源旧资产链。 - [x] 7.4 更新换货相关 handler 注册与文档生成器接入,确保 `cmd/api/docs.go`、`cmd/gendocs/main.go` 与最新接口契约保持一致。 - [x] 7.5 为历史记录补兼容逻辑:`flow_type` 为空按 `shipping`,历史已完成单据 `completed_at` 为空时追溯回退 `updated_at`。 ## 8. 手工验证与交付检查 - [x] 8.1 执行 `go build ./...`,确认换货相关模型、DTO、Service、Handler、路由和文档生成器改动可正常编译。 - [x] 8.2 使用 PostgreSQL MCP 核对 `tb_exchange_order` 字段、默认值、索引和时间字段是否与提案一致。 - [ ] 8.3 手工验证 `shipping` 流程:创建、客户端填地址、后台发货、后台完成、取消限制、`migrate_data=true/false`、客户绑定切换、旧资产状态、新资产状态。 - [ ] 8.4 手工验证 `direct` 流程:创建即完成、`iot_card/device` 两类资产、`migrate_data=true/false`、绑定不可承接时回滚、已完成单据不出现在客户端待处理。 - [ ] 8.5 手工验证 `renew` 与资产追溯:旧资产可转新、`generation` 仅在 `renew` 递增、`include_previous=true` 能正确返回旧资产链及真实 `completed_at`。 - [ ] 8.6 手工验证新增边界:旧资产非已销售拒绝、新旧资产 `shop_id` 不一致拒绝、新资产已绑定/已占用拒绝、旧钱包存在冻结余额时迁移拒绝、direct 失败不落半成品单据。