Files
junhong_cmp_fiber/docs/commission/accumulated-trigger-logic.md
huang 2b0f79be81
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m45s
归档一次性佣金配置落库与累计触发修复,同步规范文档到主 specs
- 归档 fix-one-time-commission-config-and-accumulation 到 archive/2026-01-29-*
- 同步 delta specs 到主规范(one-time-commission-trigger、commission-calculation)
- 新增累计触发逻辑文档和测试用例
- 修复一次性佣金配置落库和累计充值更新逻辑
2026-01-29 16:00:18 +08:00

182 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 累计触发一次性佣金逻辑流程
## 概述
一次性佣金支持两种触发方式:
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):数据库字段定义