## Context

Phase 2 完成了代理套餐分配机制，但卡和设备还没有关联到具体的套餐系列。本期在 IotCard 和 Device 模型上新增字段，记录其所属的套餐系列分配，为后续的套餐购买和佣金计算做准备。

**关键业务规则**：
- 卡/设备关联后才能购买该系列下的套餐
- 设备关联后，其绑定的所有卡共享该套餐系列（设备级套餐）
- 每张卡/设备只能触发一次一次性佣金

## Goals / Non-Goals

**Goals:**
- 在 IotCard 模型新增套餐系列关联和佣金状态字段
- 在 Device 模型新增相同字段
- 提供批量设置卡/设备套餐系列的 API
- 验证关联的系列必须是当前店铺被分配的

**Non-Goals:**
- 不实现订单支付（Phase 4）
- 不实现佣金计算（Phase 5）
- 不自动同步设备和卡的关联（手动设置）

## Decisions

### 1. 新增字段设计

**决策**：在 IotCard 和 Device 模型各新增 3 个字段

```go
// IotCard 新增字段
SeriesAllocationID    uint  `gorm:"column:series_allocation_id;index;comment:套餐系列分配ID" json:"series_allocation_id"`
FirstCommissionPaid   bool  `gorm:"column:first_commission_paid;default:false;comment:一次性佣金是否已发放" json:"first_commission_paid"`
AccumulatedRecharge   int64 `gorm:"column:accumulated_recharge;type:bigint;default:0;comment:累计充值金额(分)" json:"accumulated_recharge"`

// Device 新增字段（相同）
SeriesAllocationID    uint  `gorm:"column:series_allocation_id;index;comment:套餐系列分配ID" json:"series_allocation_id"`
FirstCommissionPaid   bool  `gorm:"column:first_commission_paid;default:false;comment:一次性佣金是否已发放" json:"first_commission_paid"`
AccumulatedRecharge   int64 `gorm:"column:accumulated_recharge;type:bigint;default:0;comment:累计充值金额(分)" json:"accumulated_recharge"`
```

**理由**：
- `series_allocation_id`：关联到 ShopSeriesAllocation，决定可购买的套餐和一次性佣金配置
- `first_commission_paid`：标记一次性佣金状态，防止重复发放（每张卡/设备仅发放一次）
- `accumulated_recharge`：累计充值金额，用于累计充值触发条件判断（trigger="accumulated_recharge"时使用）

**一次性佣金触发流程**：
1. 用户购买套餐并支付成功
2. 系统检查 `first_commission_paid` 是否为 false（未发放过）
3. 根据 `OneTimeCommissionTrigger` 判断触发条件：
   - `single_recharge`：检查本次充值金额是否 ≥ 阈值
   - `accumulated_recharge`：检查 `accumulated_recharge + 本次充值` 是否 ≥ 阈值
4. 如果触发，查询该系列分配的销售业绩（ShopSeriesCommissionStats），选择梯度档位
5. 创建佣金记录并入账
6. 标记 `first_commission_paid = true`

### 2. 设备与卡的关系

**决策**：设备和卡独立设置套餐系列

```
场景 1：单卡销售
- IotCard.series_allocation_id 有值
- 购买套餐时使用卡的 series_allocation_id

场景 2：设备销售（整机出货）
- Device.series_allocation_id 有值
- 设备下的卡可以不设置 series_allocation_id
- 购买套餐时优先使用 Device.series_allocation_id
```

**理由**：
- 单卡和设备是两种不同的销售模式
- 设备级套餐购买时，所有卡共享流量
- 佣金按设备计算，不按卡数倍增

### 3. 批量设置 API 设计

**决策**：使用 PATCH 方法批量更新

```
PATCH /api/admin/iot-cards/series-bindng
Body: { "iccids": ["xxx", "yyy"], "series_allocation_id": 123 }

PATCH /api/admin/devices/series-bindng
Body: { "device_ids": [1, 2, 3], "series_allocation_id": 123 }
```

**理由**：
- PATCH 语义合适（部分更新）
- 支持批量操作提高效率
- 通过 ICCID/设备 ID 定位资源

### 4. 权限验证

**决策**：只能关联当前店铺被分配的套餐系列

验证逻辑：
1. 获取卡/设备的 shop_id
2. 检查 series_allocation_id 对应的分配是否属于该店铺
3. 检查分配状态是否启用

**理由**：
- 防止关联未被分配的系列
- 确保数据一致性

## Risks / Trade-offs

### 风险 1：批量操作性能

**风险**：大批量设置时可能超时

**缓解**：
- 限制单次批量数量（如最多 500 条）
- 使用批量更新 SQL 而非循环单条更新

### 风险 2：设备和卡关联不一致

**风险**：设备设置了系列但卡没设置，或反过来

**缓解**：
- 购买套餐时明确优先级：设备级 > 卡级
- 查询接口明确返回实际使用的系列来源

## Open Questions

1. **是否需要清除关联功能？**
   - 当前设计：可以将 series_allocation_id 设为 0 清除关联
   - 待确认：清除后是否影响已购买的套餐？

2. **设备和卡的 accumulated_recharge 如何同步？**
   - 当前设计：设备级购买时更新 Device.accumulated_recharge
   - 单卡购买时更新 IotCard.accumulated_recharge
   - 待确认：是否需要双向同步？
