Status: done

# 无虚拟号单卡 C 端完整链路

## Parent

`.scratch/customer-binding-architecture/PRD.md`

## What to build

扩展 `CustomerBinding` 模块，使无虚拟号的独立 IoT 卡（单卡）能够完整走通 C 端业务流程：登录绑定、实名认证、购买套餐。

**仅影响 IoT 卡（单卡）。设备资产必然有虚拟号，不在本切片处理范围内。**

### 扩展 Bind()

当资产为 IoT 卡且 `virtual_no` 为空时：
- 写 `tb_personal_customer_iccid`（按 ICCID 绑定），而非 `tb_personal_customer_device`
- `PersonalCustomerICCIDStore` 已有完整实现，直接注入使用

当资产为 IoT 卡且 `virtual_no` 不为空时：行为与切片 1 一致，不变。

### 修复 firstEverBind + markAssetAsSold

当前 `firstEverBind` 通过查 `tb_personal_customer_device WHERE virtual_no = ""` 来判断是否首次绑定，对无虚拟号的卡完全失效（所有无虚拟号的卡共用同一个空 key）。

修复方式：在 `Bind()` 内，按路径分别判断首绑：
- 有虚拟号路径：查 `tb_personal_customer_device WHERE virtual_no = ?`
- 无虚拟号路径：查 `tb_personal_customer_iccid WHERE iccid = ?`

首次绑定后正常触发 `markAssetAsSold()`，将卡的 `asset_status` 从在库（1）更新为已销售（2）。

### 扩展 OwnsAsset()

当资产为 IoT 卡时：
- 若卡有 `virtual_no`：查 `tb_personal_customer_device`（与切片 1 一致）
- 若卡无 `virtual_no`：查 `tb_personal_customer_iccid WHERE iccid = ? AND status = 1`

当资产为设备时：行为不变，只查 `tb_personal_customer_device`。

## Acceptance criteria

- [ ] 无虚拟号的单卡 C 端登录后，`tb_personal_customer_iccid` 中出现对应的绑定记录
- [ ] 无虚拟号单卡首次登录后，`tb_iot_card.asset_status` 变为 2（已销售）
- [ ] 无虚拟号单卡登录后，实名认证接口返回成功（不报"无权限"）
- [ ] 无虚拟号单卡登录后，购买套餐接口归属验证通过
- [ ] 有虚拟号卡的全部行为与切片 1 完成后保持一致（无回归）
- [ ] 设备资产的全部行为不受影响

## Blocked by

`.scratch/customer-binding-architecture/issues/01-customer-binding-module-refactor.md`
