## 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 - 待确认:是否需要双向同步?