归档一次性佣金配置落库与累计触发修复,同步规范文档到主 specs
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m45s

- 归档 fix-one-time-commission-config-and-accumulation 到 archive/2026-01-29-*
- 同步 delta specs 到主规范(one-time-commission-trigger、commission-calculation)
- 新增累计触发逻辑文档和测试用例
- 修复一次性佣金配置落库和累计充值更新逻辑
This commit is contained in:
2026-01-29 16:00:18 +08:00
parent d977000a66
commit 2b0f79be81
19 changed files with 1654 additions and 136 deletions

View File

@@ -0,0 +1,181 @@
# 累计触发一次性佣金逻辑流程
## 概述
一次性佣金支持两种触发方式:
1. **单次充值触发**:单笔订单金额达到阈值即发放
2. **累计充值触发**:累计多次充值金额达到阈值后发放(本文档重点)
## 累计触发流程
```
┌─────────────────────────────────────────────────────────────┐
│ 用户购买套餐 │
└────────────────────┬────────────────────────────────────────┘
┌────────────▼──────────────┐
│ 支付成功触发佣金计算 │
└────────────┬──────────────┘
┌────────────▼──────────────┐
│ 读取一次性佣金配置 │
│ - EnableOneTimeCommission │
│ - Trigger = accumulated │
│ - Threshold 阈值 │
└────────────┬──────────────┘
┌────────────▼──────────────┐
│ 累计金额 += 本次订单金额 │
│ 写回 AccumulatedRecharge │
└────────────┬──────────────┘
┌────────────▼──────────────┐
│ 累计金额 >= 阈值? │
└─────┬───────────┬──────────┘
│ 否 │ 是
│ │
│ ┌────▼───────────────┐
│ │ FirstCommissionPaid│
│ │ 已标记? │
│ └────┬───────┬───────┘
│ │ 是 │ 否
│ │ │
│ ┌────▼────┐ │
│ │ 跳过 │ │
│ └─────────┘ │
│ │
│ ┌────▼────────┐
│ │ 计算佣金金额 │
│ └────┬────────┘
│ │
│ ┌────▼────────┐
│ │ 创建佣金记录 │
│ │ 佣金入账 │
│ └────┬────────┘
│ │
│ ┌────▼────────┐
│ │ 标记 │
│ │FirstCommission│
│ │Paid = true │
│ └────┬────────┘
│ │
└───────────────────▼
┌────────────▼──────────────┐
│ 完成 │
└───────────────────────────┘
```
## 关键字段
### IotCard / Device 模型
| 字段 | 类型 | 说明 |
|------|------|------|
| `accumulated_recharge` | int64 | 累计充值金额(分),每次支付成功都会累加 |
| `first_commission_paid` | bool | 一次性佣金是否已发放,防止重复发放 |
| `series_allocation_id` | uint | 关联的系列分配ID用于获取配置 |
### ShopSeriesAllocation 模型
| 字段 | 类型 | 说明 |
|------|------|------|
| `enable_one_time_commission` | bool | 是否启用一次性佣金 |
| `one_time_commission_trigger` | string | 触发方式:`single_recharge``accumulated_recharge` |
| `one_time_commission_threshold` | int64 | 最低阈值(分) |
| `one_time_commission_type` | string | 佣金类型:`fixed``tiered` |
| `one_time_commission_mode` | string | 返佣模式:`fixed``percent` |
| `one_time_commission_value` | int64 | 佣金值(分或千分比) |
## 核心逻辑实现
### 1. 累计金额更新
**位置**`internal/service/commission_calculation/service.go`
```go
// 累计充值触发场景:每次支付都写回累计金额
if allocation.OneTimeCommissionTrigger == model.OneTimeCommissionTriggerAccumulatedRecharge {
newAccumulated := card.AccumulatedRecharge + order.TotalAmount
if err := tx.Model(&model.IotCard{}).Where("id = ?", cardID).
Update("accumulated_recharge", newAccumulated).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新卡累计充值金额失败")
}
card.AccumulatedRecharge = newAccumulated
}
```
### 2. 阈值判断与发放
```go
// 判断是否已发放过
if card.FirstCommissionPaid {
return nil // 已发放,跳过
}
// 根据触发方式选择充值金额
var rechargeAmount int64
switch allocation.OneTimeCommissionTrigger {
case model.OneTimeCommissionTriggerSingleRecharge:
rechargeAmount = order.TotalAmount // 单次金额
case model.OneTimeCommissionTriggerAccumulatedRecharge:
rechargeAmount = card.AccumulatedRecharge // 累计金额
default:
return nil
}
// 判断是否达到阈值
if rechargeAmount < allocation.OneTimeCommissionThreshold {
return nil // 未达阈值,不发放
}
// 计算佣金、创建记录、入账
commissionAmount, err := s.calculateOneTimeCommission(ctx, allocation, order.TotalAmount)
// ...
// 标记已发放
if err := tx.Model(&model.IotCard{}).Where("id = ?", cardID).
Update("first_commission_paid", true).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "更新卡佣金发放状态失败")
}
```
## 典型场景示例
### 场景:阈值 10000 分100元佣金 500 分5元
| 支付次数 | 订单金额 | 累计金额 | 是否发放佣金 | 说明 |
|---------|---------|---------|-------------|------|
| 第1次 | 3000 | 3000 | ❌ | 未达阈值 |
| 第2次 | 4000 | 7000 | ❌ | 未达阈值 |
| 第3次 | 4000 | 11000 | ✅ 发放500 | 达到阈值,首次发放 |
| 第4次 | 3000 | 14000 | ❌ | 已发放过,不重复 |
| 第5次 | 5000 | 19000 | ❌ | 已发放过,不重复 |
## 注意事项
1. **每次支付都写回累计金额**:即使未达阈值,也要更新 `accumulated_recharge`
2. **仅发放一次**:通过 `first_commission_paid` 标记防止重复发放
3. **事务保证一致性**:累计金额更新、佣金记录创建、标记更新都在同一事务中
4. **适用于单卡和设备**`IotCard``Device` 模型都有相同字段,逻辑完全一致
## 测试覆盖
单元测试:`tests/unit/commission_calculation_service_test.go`
- ✅ 第一次支付:累计金额更新为 3000未发放
- ✅ 第二次支付:累计金额更新为 7000未发放
- ✅ 第三次支付:累计金额更新为 11000发放佣金 500
- ✅ 第四次支付:累计金额更新为 14000不重复发放
集成测试:`tests/integration/shop_series_allocation_test.go`
- ✅ 一次性佣金配置落库(固定类型)
- ✅ 一次性佣金配置落库(梯度类型)
- ✅ 启用一次性佣金但未提供配置应失败
## 相关文档
- [API 文档](../api-documentation-guide.md):自动生成的 OpenAPI 文档包含所有参数说明
- [DTO 规范](../dto-standards.md):一次性佣金配置的请求/响应结构
- [Model 规范](../model-standards.md):数据库字段定义