## 1. F1 一资产一号 - [x] 1.1 `pkg/errors` 新增“该资产已绑定手机号,如需更换请联系管理员”错误码,提示不含号码。 - [x] 1.2 `Establish` 写入前查询该资产有效关联:无则写入、同号幂等、他号拒绝;不加任何资产锁(Redis、行锁、应用锁都不加),手机号侧既有十项名额判断不变。 - [x] 1.3 核对所有写资产关联的入口(H5 绑定、老客户给新资产绑定)都只经 `Establish`。 ## 2. F1 H5 登录与绑定 - [x] 2.1 `need_bind_phone` 资产侧改为“当前资产是否已有有效关联”,客户无手机号仍为 true;登录响应 DTO 新增资产是否已绑定、是否本人号码两个布尔字段,不返回号码。 - [x] 2.2 拆分 H5 绑定写入,按 delta spec 四种场景分别写客户手机号与资产关联;`bindExistingPrimaryPhone` 不再要求号码等于客户手机号。 - [x] 2.3 `ChangePhone` 去掉 `migrateAssociations` 调用及对应迁移审计,其余校验不变。 ## 3. M12 换货 - [x] 3.1 `ensureNewAssetBindingAvailableWithTx` 改查 `tb_phone_asset_association` 有效关联,用 `errors.NewWithData` 返回不含号码的错误信息与 `data.phone`;不再查客户绑定表。 - [x] 3.2 `customer_binding` 新增 `ClearForExchange`(设备虚拟号或 IMEI、卡虚拟号与 ICCID 两边都清,`status 1 → 0`,同事务审计);`switchCustomerBindingWithTx` 改调用它;删除 `Migrate`、`migrateFromPCD`、`migrateFromPCI`。 - [x] 3.3 `pkg/constants/phone_asset_association.go` 新增失效方式 `exchange_completed`;换货完成事务内用 `LockValidByAsset` + `InvalidateByIDs` 失效旧资产有效关联,失效原因“换货完成”,操作人为确认完成账号,写审计(手机号脱敏)。 - [x] 3.4 更新换货接口说明(`data.phone`)与 H5 登录、绑定接口说明。 ## 4. 文档与测试环境验证 - [x] 4.1 `gofmt`、`go build ./cmd/api ./cmd/worker`、`go run cmd/gendocs/main.go`。 - [x] 4.2 只读统计测试库“同一资产多条有效关联”,有则出清单。 - [x] 4.3 F1(按 ENG-TEST-001,关联只经 H5 短信验证建立,不直接改库):资产已有号码 A 时任何客户进入资产侧不要求绑定;号码 B 绑定被拒、错误码与提示正确且不含号码;A 重复验证幂等;四种 H5 场景按 delta spec 写入;客户换号后资产关联不变;后台解除后资产侧重新要求绑定、B 可绑定;`associated_phones` 与审计正确。 - [x] 4.4 M12:给一台测试设备经 H5 建立关联后,直接换货被拒、不产生换货单、`data.phone` 正确且日志无完整号码 → 后台解除 → 重新提交成功;旧资产客户绑定全部无效、审计写入、原客户用旧令牌访问旧资产被拒、原客户重新扫码旧资产可正常进入且不重复标记已售;新资产不新增客户绑定;卡换货虚拟号与 ICCID 两种绑定都被清空;客户手机号不变;无关联但多人扫码的资产可直接换货;类型不一致、状态不可换、已被占用仍被拒;物流换货确认完成时被拒、解除后成功;完成后旧资产关联失效(`exchange_completed`)、名额释放。 - [ ] 4.5 通知测试环境前端接入登录响应两个新布尔字段与换货 `data.phone`。 - [ ] 4.6 运行 `openspec validate --all` 与 `./scripts/context-health.sh`。