# Phase 3: 设备体系完善 - Context

**Gathered:** 2026-03-28
**Status:** Ready for planning

<domain>
## Phase Boundary

扩展设备数据模型（D-0）、对接 Gateway sync-info 同步接口（D-1）、建立"当前使用卡"标识（D-2）、将刷新接口接入 sync-info 完成端到端数据更新（D-3）。

全部为修复性/完善性变更，无新业务模块。仅影响设备相关模型、Gateway 客户端、资产刷新服务。

</domain>

<decisions>
## Implementation Decisions

### Plan 分组策略

- **D-01:** 拆分为 **2 个 Plan**：
  - **Plan 1（基础层）**：D-0（迁移+模型+DTO）+ D-1（Gateway SyncDeviceInfo 方法）+ D-2（is_current 字段）
  - **Plan 2（功能层）**：D-3（Refresh 接入 sync-info + updateDeviceFromSyncInfo）
- **D-02:** Plan 1 内部执行顺序：D-0 先行（DB 迁移、Device Model、DeviceSimBinding Model、DTO 全部到位）→ D-1 + D-2 并行（都依赖 D-0，互不依赖）
- **D-03:** 每个子任务独立一个 commit（延续 Phase 1/2 粒度规范）

### DB 迁移

- **D-04:** 迁移编号从 `000090` 起，拆为 2 个迁移文件：
  - `000090_device_fields_extension`：tb_device 新增 5 个字段
  - `000091_device_sim_binding_is_current`：tb_device_sim_binding 新增 is_current 字段
- **D-05:** 使用 `.up.sql` / `.down.sql` 双文件形式（与现有迁移规范一致）

### Gateway 类型定义位置

- **D-06:** `SyncDeviceInfoReq` / `SyncDeviceInfoResp` 放在 **`internal/gateway/models.go`**（与现有 DeviceInfoReq / SlotInfoResp 等结构保持风格一致）
- **D-07:** `SyncDeviceInfo()` 方法放在 `internal/gateway/device.go`（与现有 GetDeviceInfo / GetSlotInfo 等方法一起）
- 修正文档中将类型嵌入 device.go 代码片段属于示意，不代表最终位置

### 实时字段暴露范围

- **D-08:** Refresh 响应中**不暴露**非存储实时字段（rssi / battery_level / ssid / wifi_enabled 等）
- **D-09:** 实时字段仅在调用 sync-info 时**写入 DB 存储字段**（online_status / last_online_time / software_version / switch_mode / last_gateway_sync_at）
- **D-10:** 这满足所有成功标准（标准 2、4 只要求存储字段同步）

### is_current 字段的 DTO 同步范围

- **D-11:** `is_current` 同步到**两处 DTO**：
  - `BoundCardInfo`（`internal/model/dto/asset_dto.go`）—— 资产实时状态 Refresh 响应中的绑定卡列表
  - `DeviceCardBindingResponse`（`internal/model/dto/device_dto.go`）—— 设备管理页 ListDeviceCards 接口
- **D-12:** 两处同步，保持前端两个场景下的数据一致

### updateDeviceFromSyncInfo 实现细节

- **D-13:** `updateDeviceFromSyncInfo` 为私有函数，位于 `internal/service/asset/service.go`
- **D-14:** is_current 更新使用**事务**：先全部设为 false，再把 current_iccid 对应行设为 true（原子操作，防止中间状态）
- **D-15:** sync-info 调用失败时**不阻断刷新流程**，仅记录 Warn 日志（`sync-info 调用失败`）

### Agent's Discretion

- `last_online_time` / `last_gateway_sync_at` 字段 parsing（sync-info 返回字符串，需转换为 time.Time）的具体格式，agent 参考 Gateway 文档中的 `last_update_time` 示例格式
- `DeviceRealtimeInfo` 独立 DTO 结构**不创建**（不暴露实时字段，无需此结构）
- `updateDeviceFromSyncInfo` 的 DB 更新使用 GORM `Updates(map[string]any{...})`，不用 `Save()`（避免全字段覆写）

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### 修复规格（核心参考）
- `.sisyphus/plans/修正业务-完整方案.md` — 完整修复规格书，含每个需求的代码片段和人工验收清单：
  - 方案 D（DEVICE-01~04）：约第 955-1130 行（D-0 字段策略 + D-1 Gateway 接口 + D-2 is_current + D-3 Refresh 接入）

### Gateway 接口文档
- `docs/第三方文档/gateway设备详情同步接口.md` — sync-info 接口完整字段说明（data 字段映射表、cardNo 规则、认证方式）

### 需要修改的核心文件
- `internal/model/device.go` — D-0：新增 5 个 DB 字段
- `internal/model/device_sim_binding.go` — D-2：新增 is_current 字段
- `internal/model/dto/asset_dto.go` — D-2：BoundCardInfo 新增 is_current
- `internal/model/dto/device_dto.go` — D-0：DeviceResponse 新增 5 个字段；D-2：DeviceCardBindingResponse 新增 is_current
- `internal/gateway/models.go` — D-1：新增 SyncDeviceInfoReq / SyncDeviceInfoResp
- `internal/gateway/device.go` — D-1：新增 SyncDeviceInfo() 方法
- `internal/service/asset/service.go` — D-3：Refresh device 分支追加 sync-info 调用；新增 updateDeviceFromSyncInfo()
- `migrations/` — D-0：000090_device_fields_extension；D-2：000091_device_sim_binding_is_current

### 现有参考实现
- `internal/service/asset/service.go:Refresh()` — 约第 295 行，现有 device 刷新分支（追加 sync-info 调用入口）

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `internal/gateway/client.go:doRequestWithResponse[T]()` — 泛型请求方法，SyncDeviceInfo 直接复用（与 GetDeviceInfo 相同调用模式）
- `internal/service/asset/service.go:Refresh()` — 已有 device 冷却 Key + 绑定卡遍历框架，D-3 在此追加 sync-info 调用
- `internal/service/asset/service.go:deviceSimBindingStore` — 已注入，D-3 的 updateDeviceFromSyncInfo 可直接使用

### Established Patterns
- Gateway 类型分离：结构体在 `models.go`，方法在对应领域文件（device.go / flow_card.go 等）
- DB 更新：使用 `Updates(map[string]any{...})` + `Model(&T{})` + `Where`，不用 `Save()`
- 错误处理：非关键步骤失败用 `logger.Warn`，关键步骤用 `errors.Wrap`

### Integration Points
- `internal/gateway/device.go` — 新增 SyncDeviceInfo 方法
- `internal/service/asset/service.go:Refresh()` device 分支 —— sync-info 调用入口（冷却 Key 设置之前，已遍历完绑定卡之后）
- `BoundCardInfo` 在 `asset_dto.go`（被 AssetRealtimeStatusResponse 引用，多个 C 端和 B 端接口共用）

</code_context>

<specifics>
## Specific Ideas

- 修正文档中 D-3 的代码片段提供了 `updateDeviceFromSyncInfo` 的函数骨架，agent 应直接参考（文件：`.sisyphus/plans/修正业务-完整方案.md` 方案 D-3 段落）
- `SyncDeviceInfoResp.LastOnlineTime` 字段类型用 `string`（Gateway 返回字符串），在 updateDeviceFromSyncInfo 内按需 Parse

</specifics>

<deferred>
## Deferred Ideas

- 设备实时字段（rssi / battery_level / ssid 等）暴露到 Refresh 响应 —— 超出当前成功标准，考虑 Phase 4+ 或按需补充
- DeviceRealtimeInfo 独立 DTO 结构 —— 若未来暴露实时字段时再创建

</deferred>

---

*Phase: 03-device-system*
*Context gathered: 2026-03-28*
