## Context

当前系统支持"单卡授权企业用户"功能，通过 `tb_enterprise_card_authorization` 表记录卡与企业的授权关系。但现有实现存在以下问题：

1. **设备维度缺失**：企业用户只能看到卡列表，无法以设备为单位管理资产
2. **逻辑不一致**：单卡授权入口支持 DeviceBundle（确认后授权设备下所有卡），但没有独立的设备授权概念
3. **记录关联缺失**：无法追踪卡授权是通过单独授权还是设备授权创建的

现有相关模块：
- `Device` 模型：设备表 `tb_device`，通过 `shop_id` 标识所有权
- `DeviceSimBinding` 模型：设备-卡绑定关系表，一设备最多绑定 4 张卡
- `EnterpriseCardAuthorization` 模型：卡授权表
- 设备分销功能：`AllocateDevices` 将设备分销给代理店铺（修改 `shop_id`）

## Goals / Non-Goals

**Goals:**
- 支持以设备为单位授权给企业，自动授权设备下所有已绑定的卡
- 一个设备同一时间只能授权给一个企业（唯一性约束）
- 授权设备时自动创建卡授权记录，回收时同步回收
- 卡授权记录关联设备授权，支持追溯授权来源
- 企业端可以查看设备列表、设备详情及其绑定的卡
- 企业端可以对设备下的卡进行停机/复机操作
- 单卡授权入口禁止授权已绑定设备的卡

**Non-Goals:**
- 不涉及设备分销逻辑（设备 → 店铺，已有功能）
- 不涉及设备级别的停机/复机（仍然是卡级别操作）
- 不涉及设备解绑卡的功能（企业只能查看，不能解绑）
- 不涉及设备钱包或套餐购买功能

## Decisions

### 1. 新增独立的设备授权表

**决策**：创建 `tb_enterprise_device_authorization` 表，与卡授权表结构类似

**理由**：
- 设备授权是独立的业务概念，需要独立的授权记录
- 支持设备级别的授权/回收操作和记录查询
- 与卡授权表解耦，职责清晰

**替代方案**：
- ❌ 在卡授权表中添加 device_id 字段：无法表达"设备授权"这个独立概念，回收设备时逻辑复杂
- ❌ 只用卡授权表+标记字段：无法追踪设备授权的元信息（授权人、时间等）

### 2. 卡授权表添加 device_auth_id 关联字段

**决策**：在 `tb_enterprise_card_authorization` 表添加 `device_auth_id` 字段

**理由**：
- 明确区分卡授权来源：单卡授权（NULL）vs 设备授权（有值）
- 回收设备授权时可以精确定位需要回收的卡授权记录
- 支持查询"某设备授权下的所有卡"

**表结构变更**：
```sql
ALTER TABLE tb_enterprise_card_authorization 
ADD COLUMN device_auth_id BIGINT DEFAULT NULL;

CREATE INDEX idx_eca_device_auth ON tb_enterprise_card_authorization(device_auth_id);
```

### 3. 设备授权唯一性约束

**决策**：使用部分唯一索引保证一个设备同时只能授权给一个企业

**实现**：
```sql
CREATE UNIQUE INDEX uq_active_device_auth 
ON tb_enterprise_device_authorization(device_id) 
WHERE revoked_at IS NULL AND deleted_at IS NULL;
```

**理由**：
- 允许历史授权记录（已回收的）存在多条
- 只限制"当前有效"的授权唯一性
- 与卡授权的设计模式一致

### 4. 授权联动机制

**决策**：授权设备时在同一事务内创建设备授权和卡授权记录

**流程**：
```
授权设备 → 事务开始
         → 创建 EnterpriseDeviceAuthorization
         → 获取 device_auth_id
         → 查询设备下所有已绑定的卡
         → 批量创建 EnterpriseCardAuthorization (device_auth_id = 上一步的ID)
         → 事务提交
```

**回收流程**：
```
回收设备 → 事务开始
         → 更新 EnterpriseDeviceAuthorization.revoked_at
         → 批量更新关联的 EnterpriseCardAuthorization.revoked_at
         → 事务提交
```

### 5. 单卡授权入口修改

**决策**：移除 DeviceBundle 支持，禁止授权已绑定设备的卡

**修改点**：
- `Service.AllocateCards`：移除 DeviceBundle 预检和处理逻辑
- `Service.AllocateCardsPreview`：直接返回错误而非 DeviceBundle
- DTO：移除 `DeviceBundle`、`ConfirmDeviceBundles` 等相关结构

**理由**：
- 职责分离：单卡授权只处理独立卡，设备授权处理设备
- 避免逻辑混淆：用户不会再在单卡入口看到设备相关提示
- 简化代码：移除复杂的 DeviceBundle 处理逻辑

**BREAKING CHANGE**：前端单卡授权页面需要适配，不再支持确认设备包

### 6. API 路径设计

**后台管理（Admin）**：
```
POST /api/admin/enterprises/:id/allocate-devices   # 授权设备
POST /api/admin/enterprises/:id/recall-devices     # 回收设备
GET  /api/admin/enterprises/:id/devices            # 设备列表
```

**企业端（H5）**：
```
GET  /api/h5/enterprise/devices                              # 设备列表
GET  /api/h5/enterprise/devices/:device_id                   # 设备详情
POST /api/h5/enterprise/devices/:device_id/cards/:card_id/suspend  # 停机
POST /api/h5/enterprise/devices/:device_id/cards/:card_id/resume   # 复机
```

**理由**：
- 与现有单卡授权 API 风格一致（`/enterprises/:id/allocate-cards`）
- H5 端使用 `/enterprise/devices` 而非 `/enterprises/:id/devices`，因为企业用户只能访问自己的资源

### 7. 权限控制

**后台管理**：
- 平台用户：可以授权任意设备给任意企业
- 代理用户：只能授权自己店铺的设备给自己店铺下的企业

**企业端**：
- 只能访问授权给自己企业的设备
- 通过 GORM Callback 自动过滤

## Risks / Trade-offs

### [风险] 单卡授权入口 Breaking Change
**影响**：前端单卡授权页面行为变更，不再支持授权设备卡
**缓解**：
- 提前通知前端团队
- 返回明确的错误信息引导用户使用设备授权入口
- 可选：在错误响应中返回涉及的设备信息，方便前端跳转

### [风险] 数据迁移
**影响**：如果现有数据中有通过单卡授权入口授权的设备卡，无法追溯来源
**缓解**：
- 新字段 `device_auth_id` 默认 NULL，兼容历史数据
- 历史数据视为"单卡授权"，行为不变
- 无需数据迁移脚本

### [权衡] 回收粒度
**选择**：回收设备授权时同步回收所有关联的卡授权
**权衡**：不支持只回收设备授权但保留卡授权
**理由**：简化业务逻辑，保持授权关系一致性

### [权衡] 设备新增卡后的处理
**场景**：设备已授权给企业后，又绑定了新的卡
**选择**：新卡不自动授权，需要重新授权设备或单独处理
**理由**：避免隐式授权带来的安全风险，保持授权行为显式可控

## Open Questions

1. **设备授权记录管理页面**：是否需要独立的设备授权记录列表页面（类似现有的卡授权记录页面）？
   - 建议：本期先不做，通过企业设备列表满足基本需求

2. **批量操作限制**：单次授权/回收设备的数量上限？
   - 建议：与单卡授权一致，最多 100 个设备
