## Context

现有代理开放接口（`internal/service/agent_open_api/service.go`）已实现卡维度的流量查询、状态查询、实名查询和钱包购买。设备维度的 Gateway 操作（切网、重启、恢复出厂）已在 `internal/service/device/gateway_service.go` 中实现，设备级套餐流量查询通过 `PackageUsageStore.ListByCarrier(ctx, "device", deviceID, ...)` 支持。

本次变更在现有 `agent_open_api` service 中扩展设备维度能力，复用已有的 device service 和 package usage store，不引入新的数据模型或迁移。

## Goals / Non-Goals

**Goals:**
- 新增 4 个代理开放接口：设备流量查询、切网、重启、恢复出厂
- 复用现有 device service 的 Gateway 调用逻辑，不重复实现
- 权限校验与卡接口保持一致（`shop_id IN SubordinateShopIDs`）
- 设备标识符支持虚拟号和 IMEI

**Non-Goals:**
- 不新增数据库表或迁移
- 不修改现有卡接口
- 不支持 SN 作为设备标识符（开放接口只暴露虚拟号/IMEI）
- 不支持批量设备操作

## Decisions

### 决策一：agent_open_api service 注入 device.Service 依赖

**选择**：在 `agent_open_api.Service` 结构体中新增 `deviceService *device.Service` 字段，通过构造函数注入。

**理由**：device service 已封装了 Gateway 调用、审计日志和设备查询逻辑，直接复用避免重复实现。相比新建独立函数，注入 service 更符合项目分层规范。

**替代方案**：在 agent_open_api service 中直接注入 deviceStore 和 gatewayClient，自行实现权限校验和 Gateway 调用。缺点是重复了 device service 中已有的审计日志和错误处理逻辑。

### 决策二：设备权限校验方式

**选择**：查询设备后，手动检查 `device.ShopID != nil && SubordinateShopIDs 包含 *device.ShopID`。

**理由**：`ApplyShopFilter` 作用于 GORM query，而 device service 的 `GetByIdentifier` 内部已有自己的查询逻辑。为避免侵入 device service，在 agent_open_api service 层做显式权限校验，与卡接口的 `resolveOpenAPICard` 模式一致。

**实现**：新增 `resolveOpenAPIDevice(ctx, deviceNo)` 私有方法，复用 `device.Service.GetDeviceByIdentifier`，然后校验 shop_id。

### 决策三：设备流量查询复用 PackageUsageStore

**选择**：直接在 `agent_open_api.Service` 中调用已注入的 `packageUsageStore.ListByCarrier(ctx, "device", device.ID, &status)`。

**理由**：`packageUsageStore` 已在 agent_open_api service 中注入（用于卡流量查询），`ListByCarrier` 支持 `"device"` 类型，无需额外改动 store 层。

### 决策四：切网接口调用 device.Service.GatewaySwitchCard

**选择**：agent_open_api service 调用 `deviceService.GatewaySwitchCard(ctx, identifier, &dto.SwitchCardRequest{ICCID: req.ICCID})`。

**理由**：`GatewaySwitchCard` 已包含审计日志记录，开放接口复用可保证操作可追溯。

**注意**：`GatewaySwitchCard` 内部通过 `getGatewayDevice` 再次查询设备，存在两次查询。考虑到操作频率低，接受此开销，不做优化。

## Risks / Trade-offs

- **[风险] device service 审计日志中的操作者信息**：`GatewaySwitchCard` 等方法通过 `middleware.GetUserIDFromContext` 获取操作者 ID。开放接口认证中间件已将代理账号 ID 写入 `ContextKeyUserID`，审计日志可正确记录操作者。→ 无需额外处理。

- **[风险] 设备标识符两次查询**：`resolveOpenAPIDevice` 查一次，`GatewaySwitchCard` 内部再查一次。→ 接受，操作类接口频率低，影响可忽略。

- **[Trade-off] 不支持 SN**：admin 接口支持虚拟号/IMEI/SN，开放接口只支持虚拟号/IMEI。→ 简化外部接口，SN 是内部管理标识，不适合对外暴露。

## Migration Plan

无数据库变更，无需迁移。直接部署新版本即可。

## Open Questions

（无）
