All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s
- 新增套餐系列管理 (CRUD + 状态切换) - 新增套餐管理 (CRUD + 启用/禁用 + 上架/下架双状态) - 清理 8 个废弃分佣模型及对应数据库表 - Package 模型新增建议成本价、建议售价、上架状态字段 - 完整的 Store/Service/Handler 三层实现 - 包含单元测试和集成测试 - 归档 add-package-module change - 新增多个 OpenSpec changes (订单支付、店铺套餐分配、一次性分佣、卡设备系列绑定)
124 lines
4.2 KiB
Markdown
124 lines
4.2 KiB
Markdown
## 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`:累计充值金额,用于累计充值触发条件
|
||
|
||
### 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
|
||
- 待确认:是否需要双向同步?
|