refactor: 一次性佣金配置从套餐级别提升到系列级别
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s
主要变更: - 新增 tb_shop_series_allocation 表,存储系列级别的一次性佣金配置 - ShopPackageAllocation 移除 one_time_commission_amount 字段 - PackageSeries 新增 enable_one_time_commission 字段控制是否启用一次性佣金 - 新增 /api/admin/shop-series-allocations CRUD 接口 - 佣金计算逻辑改为从 ShopSeriesAllocation 获取一次性佣金金额 - 删除废弃的 ShopSeriesOneTimeCommissionTier 模型 - OpenAPI Tag '系列分配' 和 '单套餐分配' 合并为 '套餐分配' 迁移脚本: - 000042: 重构佣金套餐模型 - 000043: 简化佣金分配 - 000044: 一次性佣金分配重构 - 000045: PackageSeries 添加 enable_one_time_commission 字段 测试: - 新增验收测试 (shop_series_allocation, commission_calculation) - 新增流程测试 (one_time_commission_chain) - 删除过时的单元测试(已被验收测试覆盖)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# 累计充值追踪
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 按卡/设备按系列累计
|
||||
|
||||
系统 SHALL 按照卡/设备在每个套餐系列下独立追踪累计充值金额。不同系列的累计互不影响。
|
||||
|
||||
#### Scenario: 同一卡在不同系列的累计
|
||||
- **WHEN** IoT卡 A 在系列1下充值 100 元
|
||||
- **AND** IoT卡 A 在系列2下充值 50 元
|
||||
- **THEN** 系列1的累计金额 SHALL 为 100 元
|
||||
- **AND** 系列2的累计金额 SHALL 为 50 元
|
||||
|
||||
#### Scenario: 同一卡在同一系列的累计
|
||||
- **WHEN** IoT卡 A 在系列1下第一次充值 100 元
|
||||
- **AND** IoT卡 A 在系列1下第二次充值 50 元
|
||||
- **THEN** 系列1的累计金额 SHALL 为 150 元
|
||||
|
||||
### Requirement: 只有充值操作累计
|
||||
|
||||
系统 SHALL 只累计"充值到钱包"的操作,直接购买套餐(不经过钱包)SHALL NOT 累计。
|
||||
|
||||
#### Scenario: 直接充值累计
|
||||
- **WHEN** 客户选择充值 100 元到钱包
|
||||
- **AND** 支付成功
|
||||
- **THEN** 累计金额 SHALL 增加 100 元
|
||||
|
||||
#### Scenario: 直接购买不累计
|
||||
- **WHEN** 客户直接购买 100 元套餐(余额足够)
|
||||
- **AND** 系统从钱包扣款
|
||||
- **THEN** 累计金额 SHALL 保持不变
|
||||
|
||||
#### Scenario: 强充购买累计
|
||||
- **WHEN** 客户通过强充购买套餐
|
||||
- **AND** 强充金额为 200 元
|
||||
- **AND** 套餐价格为 100 元
|
||||
- **THEN** 累计金额 SHALL 增加 200 元(充值部分)
|
||||
- **AND** 钱包余额增加 200 后扣除 100
|
||||
|
||||
### Requirement: 首充状态按系列追踪
|
||||
|
||||
系统 SHALL 按照卡/设备在每个套餐系列下独立追踪首充状态。一个系列触发首充后,其他系列的首充状态不受影响。
|
||||
|
||||
#### Scenario: 首次在系列下充值
|
||||
- **WHEN** IoT卡 A 从未在系列1下充值过
|
||||
- **AND** IoT卡 A 进行充值操作
|
||||
- **THEN** 系统 SHALL 标记该卡在系列1下的首充状态为"已触发"
|
||||
|
||||
#### Scenario: 非首次在系列下充值
|
||||
- **WHEN** IoT卡 A 已在系列1下触发过首充
|
||||
- **AND** IoT卡 A 再次充值
|
||||
- **THEN** 系统 SHALL 不触发首充返佣
|
||||
- **AND** 首充状态保持"已触发"
|
||||
|
||||
#### Scenario: 不同系列首充独立
|
||||
- **WHEN** IoT卡 A 已在系列1下触发过首充
|
||||
- **AND** IoT卡 A 首次在系列2下充值
|
||||
- **THEN** 系统 SHALL 触发系列2的首充返佣(如果规则启用)
|
||||
|
||||
### Requirement: 一次性佣金只触发一次
|
||||
|
||||
每张卡/设备在每个套餐系列下,一次性佣金(无论首充还是累计充值)SHALL 只触发一次。触发后不再重复触发。
|
||||
|
||||
#### Scenario: 累计充值达标后不再触发
|
||||
- **WHEN** 系列规则为累计充值 200 返 40
|
||||
- **AND** IoT卡 A 累计充值达到 200 元
|
||||
- **AND** 系统触发一次性佣金 40 元
|
||||
- **AND** IoT卡 A 继续充值 100 元(累计 300 元)
|
||||
- **THEN** 系统 SHALL 不再触发一次性佣金
|
||||
@@ -0,0 +1,58 @@
|
||||
# 代理可用套餐变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 返回代理视角的套餐信息
|
||||
|
||||
代理查询套餐列表时,系统 SHALL 返回该代理视角的成本价和一次性佣金金额,而非原始配置。
|
||||
|
||||
**变更说明**:成本价和一次性佣金金额需要根据套餐分配关系动态计算。
|
||||
|
||||
#### Scenario: 代理查看套餐列表
|
||||
- **WHEN** 代理A调用套餐列表接口
|
||||
- **AND** 该套餐的基础成本价100元
|
||||
- **AND** 平台给A分配时设置成本价120元
|
||||
- **THEN** 返回的 `cost_price` SHALL 为 120元(A的成本价)
|
||||
|
||||
#### Scenario: 代理查看一次性佣金
|
||||
- **WHEN** 代理A调用套餐列表接口
|
||||
- **AND** 系列规则:首充100返20元
|
||||
- **AND** 平台给A设置的一次性佣金金额为15元
|
||||
- **THEN** 返回的 `one_time_commission_amount` SHALL 为 15元
|
||||
- **AND** 不返回系列规则的20元
|
||||
|
||||
#### Scenario: 平台查看套餐列表
|
||||
- **WHEN** 平台管理员调用套餐列表接口
|
||||
- **THEN** 返回基础成本价(不是代理视角)
|
||||
- **AND** 返回完整的一次性佣金规则
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 未分配套餐不可见
|
||||
|
||||
代理只能看到已分配给自己的套餐。未分配的套餐 SHALL NOT 出现在代理的套餐列表中。
|
||||
|
||||
#### Scenario: 只返回已分配套餐
|
||||
- **WHEN** 代理A调用套餐列表接口
|
||||
- **AND** 系统共有套餐 P1、P2、P3
|
||||
- **AND** 只有 P1、P2 分配给了 A
|
||||
- **THEN** 返回列表只包含 P1、P2
|
||||
- **AND** 不包含 P3
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 套餐分配新增一次性佣金金额
|
||||
|
||||
ShopPackageAllocation 模型 MUST 新增 `one_time_commission_amount` 字段,记录给该代理的一次性佣金金额。
|
||||
|
||||
**变更说明**:一次性佣金金额配置从系列分配移到套餐分配。
|
||||
|
||||
#### Scenario: 分配套餐时设置一次性佣金金额
|
||||
- **WHEN** 上级给下级分配套餐
|
||||
- **AND** 设置一次性佣金金额为10元
|
||||
- **THEN** ShopPackageAllocation 记录 `one_time_commission_amount = 1000`(分)
|
||||
|
||||
#### Scenario: 一次性佣金金额约束
|
||||
- **WHEN** 上级给下级设置一次性佣金金额
|
||||
- **THEN** 该金额 MUST <= 上级自己能拿到的一次性佣金金额
|
||||
- **AND** 该金额 MUST >= 0
|
||||
@@ -0,0 +1,72 @@
|
||||
# 佣金计算变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 一次性佣金计算
|
||||
|
||||
系统 SHALL 按链式分配规则计算一次性佣金。每级代理实际获得的佣金 = 自己能拿到的金额 - 给下级的金额。
|
||||
|
||||
**变更说明**:从"直接发放给归属店铺"改为"链式分配给代理链上所有店铺"。
|
||||
|
||||
#### Scenario: 计算链式分配金额
|
||||
- **WHEN** 触发一次性佣金
|
||||
- **AND** 代理链为 平台 → A → A1 → A2
|
||||
- **AND** 系列规则返20元
|
||||
- **AND** A能拿到20元,给A1设置8元
|
||||
- **AND** A1能拿到8元,给A2设置5元
|
||||
- **THEN** A2实际获得 = 5元
|
||||
- **AND** A1实际获得 = 8 - 5 = 3元
|
||||
- **AND** A实际获得 = 20 - 8 = 12元
|
||||
|
||||
#### Scenario: 末端代理全额获得
|
||||
- **WHEN** 触发一次性佣金
|
||||
- **AND** A1是末端代理(无下级)
|
||||
- **AND** A1能拿到10元
|
||||
- **THEN** A1实际获得 = 10元(全额)
|
||||
|
||||
#### Scenario: 独吞场景
|
||||
- **WHEN** 触发一次性佣金
|
||||
- **AND** A给A1设置的一次性佣金金额为0元
|
||||
- **THEN** A1实际获得 = 0元
|
||||
- **AND** A实际获得 = 自己能拿到的全部金额
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 梯度佣金计算
|
||||
|
||||
系统 SHALL 根据代理当前销量/销售额所在梯度档位计算一次性佣金金额。
|
||||
|
||||
**变更说明**:新增统计范围开关(仅自己/自己+下级),梯度升级后上级获得增量。
|
||||
|
||||
#### Scenario: 按梯度计算
|
||||
- **WHEN** 触发一次性佣金
|
||||
- **AND** 代理A当前销量150(适用">=100返10元"档位)
|
||||
- **AND** A给A1设置5元
|
||||
- **THEN** A1实际获得 = 5元
|
||||
- **AND** A实际获得 = 10 - 5 = 5元
|
||||
|
||||
#### Scenario: 梯度升级
|
||||
- **WHEN** 代理A销量从150升到210
|
||||
- **AND** 适用档位从">=100返10元"变为">=200返20元"
|
||||
- **AND** A给A1设置仍为5元
|
||||
- **THEN** A1实际获得 = 5元(不变)
|
||||
- **AND** A实际获得 = 20 - 5 = 15元(增量归上级)
|
||||
|
||||
#### Scenario: 统计范围-仅自己
|
||||
- **WHEN** 梯度配置 `stat_scope = self`
|
||||
- **THEN** 只统计该代理直接产生的销量/销售额
|
||||
|
||||
#### Scenario: 统计范围-自己+下级
|
||||
- **WHEN** 梯度配置 `stat_scope = self_and_sub`
|
||||
- **THEN** 统计该代理及所有下级代理的销量/销售额之和
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 差价佣金计算
|
||||
|
||||
差价佣金计算规则不变:上级代理的佣金 = 下级成本价 - 自己成本价。
|
||||
|
||||
#### Scenario: 差价佣金计算
|
||||
- **WHEN** 代理A1销售一单
|
||||
- **AND** A的成本价120元,A1的成本价130元
|
||||
- **THEN** A的差价佣金 = 130 - 120 = 10元
|
||||
@@ -0,0 +1,61 @@
|
||||
# 一次性佣金链式分配
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 分配时设置下级一次性佣金金额
|
||||
|
||||
系统 SHALL 允许上级代理在分配套餐时设置给下级的一次性佣金金额。该金额 MUST 小于等于上级自己能拿到的一次性佣金金额,且 MUST 大于等于 0。
|
||||
|
||||
#### Scenario: 设置有效的下级佣金金额
|
||||
- **WHEN** 代理A分配套餐给代理A1,设置一次性佣金金额为 10 元
|
||||
- **AND** 代理A自己能拿到的一次性佣金为 20 元
|
||||
- **THEN** 系统 SHALL 保存该配置
|
||||
- **AND** 代理A1的一次性佣金金额记录为 10 元
|
||||
|
||||
#### Scenario: 设置超额的下级佣金金额
|
||||
- **WHEN** 代理A分配套餐给代理A1,设置一次性佣金金额为 25 元
|
||||
- **AND** 代理A自己能拿到的一次性佣金为 20 元
|
||||
- **THEN** 系统 SHALL 拒绝该配置
|
||||
- **AND** 返回错误"给下级的一次性佣金不能超过自己能拿到的金额"
|
||||
|
||||
#### Scenario: 设置零佣金(独吞)
|
||||
- **WHEN** 代理A分配套餐给代理A1,设置一次性佣金金额为 0 元
|
||||
- **THEN** 系统 SHALL 保存该配置
|
||||
- **AND** 代理A1的一次性佣金金额记录为 0 元
|
||||
|
||||
### Requirement: 链式佣金分配计算
|
||||
|
||||
当一次性佣金触发时,系统 SHALL 沿代理链向上计算并分配佣金。每级代理实际获得的佣金 = 自己能拿到的金额 - 给下级的金额。
|
||||
|
||||
#### Scenario: 三级代理链佣金分配
|
||||
- **WHEN** 代理链为 平台 → A → A1 → A2
|
||||
- **AND** 系列规则:首充100返20元
|
||||
- **AND** 平台给A设置:20元
|
||||
- **AND** A给A1设置:8元
|
||||
- **AND** A1给A2设置:5元
|
||||
- **AND** A2的客户触发首充
|
||||
- **THEN** A2 SHALL 获得 5 元
|
||||
- **AND** A1 SHALL 获得 8 - 5 = 3 元
|
||||
- **AND** A SHALL 获得 20 - 8 = 12 元
|
||||
- **AND** 总分配金额 = 20 元
|
||||
|
||||
#### Scenario: 末端代理无下级
|
||||
- **WHEN** 代理A1是末端代理(无下级)
|
||||
- **AND** A1的客户触发首充
|
||||
- **AND** A1能拿到的一次性佣金为 10 元
|
||||
- **THEN** A1 SHALL 获得完整的 10 元
|
||||
|
||||
### Requirement: 代理只能看到自己的一次性佣金金额
|
||||
|
||||
代理查看套餐列表时,系统 SHALL 只返回该代理能拿到的一次性佣金金额,不得返回系列规则的总金额或其他代理的配置。
|
||||
|
||||
#### Scenario: 代理A查看套餐
|
||||
- **WHEN** 代理A调用套餐列表接口
|
||||
- **AND** 该套餐的系列规则为首充100返20元
|
||||
- **AND** 平台给A设置的一次性佣金为 15 元
|
||||
- **THEN** 返回的一次性佣金金额 SHALL 为 15 元
|
||||
- **AND** 不得返回 20 元(总规则)
|
||||
|
||||
#### Scenario: 平台查看套餐
|
||||
- **WHEN** 平台管理员调用套餐列表接口
|
||||
- **THEN** 返回的一次性佣金 SHALL 显示完整规则(首充100返20元)
|
||||
@@ -0,0 +1,72 @@
|
||||
# 强充检查变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 首充强充金额计算
|
||||
|
||||
当系列启用首充一次性佣金时,强充金额 SHALL 为 max(首充要求, 套餐售价)。
|
||||
|
||||
**变更说明**:明确首充强充金额的计算公式。
|
||||
|
||||
#### Scenario: 首充要求小于套餐价格
|
||||
- **WHEN** 首充要求50元,套餐售价100元
|
||||
- **THEN** 强充金额 = 100元(取套餐价格)
|
||||
|
||||
#### Scenario: 首充要求等于套餐价格
|
||||
- **WHEN** 首充要求100元,套餐售价100元
|
||||
- **THEN** 强充金额 = 100元
|
||||
|
||||
#### Scenario: 首充要求大于套餐价格
|
||||
- **WHEN** 首充要求200元,套餐售价100元
|
||||
- **THEN** 强充金额 = 200元(取首充要求)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 累计充值强充金额计算
|
||||
|
||||
当系列启用累计充值一次性佣金且启用强充时,系统 SHALL 支持两种强充金额计算方式:固定金额和动态差额。
|
||||
|
||||
**变更说明**:新增强充金额计算方式开关。
|
||||
|
||||
#### Scenario: 固定金额模式
|
||||
- **WHEN** 强充配置 `force_calc_type = fixed`
|
||||
- **AND** `force_amount = 10000`(100元)
|
||||
- **THEN** 强充金额 = 100元(固定值)
|
||||
|
||||
#### Scenario: 动态差额模式
|
||||
- **WHEN** 强充配置 `force_calc_type = dynamic`
|
||||
- **AND** 累计要求200元
|
||||
- **AND** 当前已累计80元
|
||||
- **THEN** 强充金额 = 200 - 80 = 120元(差额)
|
||||
|
||||
#### Scenario: 动态差额已达标
|
||||
- **WHEN** 强充配置 `force_calc_type = dynamic`
|
||||
- **AND** 累计要求200元
|
||||
- **AND** 当前已累计250元
|
||||
- **THEN** 强充金额 = 0元(已达标,无需强充)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 强充流程
|
||||
|
||||
强充流程保持不变:先创建充值订单,支付成功后钱进入钱包,然后自动扣款购买套餐。
|
||||
|
||||
#### Scenario: 首充强充流程
|
||||
- **WHEN** 客户购买套餐触发首充强充
|
||||
- **AND** 强充金额200元,套餐售价100元
|
||||
- **THEN** 创建充值订单200元
|
||||
- **AND** 支付成功后钱包余额+200
|
||||
- **AND** 标记首充状态为"已触发"
|
||||
- **AND** 自动创建套餐购买订单并扣款100元
|
||||
- **AND** 触发首充返佣(按链式分配)
|
||||
- **AND** 钱包剩余100元
|
||||
|
||||
#### Scenario: 累计充值强充流程
|
||||
- **WHEN** 客户购买套餐触发累计充值强充
|
||||
- **AND** 强充金额120元,套餐售价100元
|
||||
- **THEN** 创建充值订单120元
|
||||
- **AND** 支付成功后钱包余额+120
|
||||
- **AND** 累计金额 += 120
|
||||
- **AND** 自动创建套餐购买订单并扣款100元
|
||||
- **AND** 如果累计达标则触发返佣
|
||||
- **AND** 钱包剩余20元
|
||||
@@ -0,0 +1,99 @@
|
||||
# 一次性佣金触发变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 一次性充值触发佣金
|
||||
|
||||
系统 SHALL 支持"首充"触发条件:当该卡/设备在该套餐系列下首次充值且金额 ≥ 配置阈值时触发一次性佣金。
|
||||
|
||||
**变更说明**:将 `single_recharge` 重命名为 `first_recharge`(首充),强调是"第一次"充值而非"单次"充值。
|
||||
|
||||
#### Scenario: 首充达到阈值
|
||||
- **WHEN** IoT卡在该系列下首次充值 500 元
|
||||
- **AND** 配置阈值 300 元
|
||||
- **AND** 该卡在该系列下未触发过首充返佣
|
||||
- **THEN** 系统按链式分配规则发放一次性佣金
|
||||
- **AND** 标记该卡在该系列下的首充状态为"已触发"
|
||||
|
||||
#### Scenario: 首充未达到阈值
|
||||
- **WHEN** IoT卡在该系列下首次充值 200 元
|
||||
- **AND** 配置阈值 300 元
|
||||
- **THEN** 系统不发放一次性佣金
|
||||
- **AND** 首充状态保持"未触发"
|
||||
|
||||
#### Scenario: 非首次充值
|
||||
- **WHEN** IoT卡在该系列下已触发过首充
|
||||
- **AND** 再次充值 500 元(≥阈值)
|
||||
- **THEN** 系统不发放一次性佣金
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 累计充值触发佣金
|
||||
|
||||
系统 SHALL 支持"累计充值"触发条件:当卡/设备在该套餐系列下的累计充值金额 ≥ 配置阈值时触发一次性佣金。
|
||||
|
||||
**变更说明**:累计范围改为按"套餐系列"累计,而非全局累计。只有充值操作累计,直接购买套餐不累计。
|
||||
|
||||
#### Scenario: 累计达到阈值
|
||||
- **WHEN** IoT卡在该系列下之前累计充值 200 元
|
||||
- **AND** 本次充值 150 元
|
||||
- **AND** 配置阈值 300 元
|
||||
- **THEN** 系统更新该系列的累计充值为 350 元
|
||||
- **AND** 累计 350 元 ≥ 300 元,系统按链式分配规则发放一次性佣金
|
||||
- **AND** 标记该卡在该系列下已触发累计充值返佣
|
||||
|
||||
#### Scenario: 累计未达到阈值
|
||||
- **WHEN** IoT卡在该系列下累计充值为 100 元
|
||||
- **AND** 本次充值 100 元
|
||||
- **AND** 配置阈值 300 元
|
||||
- **THEN** 系统更新累计充值为 200 元
|
||||
- **AND** 累计 200 元 < 300 元,系统不发放一次性佣金
|
||||
|
||||
#### Scenario: 直接购买不累计
|
||||
- **WHEN** IoT卡直接购买套餐(不经过充值)
|
||||
- **THEN** 该系列的累计充值金额不变
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 一次性佣金只发放一次
|
||||
|
||||
每张卡/设备在每个套餐系列下,一次性佣金 SHALL 只发放一次,无论是首充还是累计充值触发。通过按系列追踪的状态字段控制。
|
||||
|
||||
**变更说明**:首充状态和累计充值触发状态改为按套餐系列追踪,不同系列互不影响。
|
||||
|
||||
#### Scenario: 首次触发
|
||||
- **WHEN** 首次满足触发条件(首充或累计充值)
|
||||
- **THEN** 按链式分配规则发放佣金
|
||||
- **AND** 设置该系列的触发状态为 true
|
||||
|
||||
#### Scenario: 再次满足条件
|
||||
- **WHEN** 再次满足触发条件
|
||||
- **AND** 该系列的触发状态已为 true
|
||||
- **THEN** 不发放佣金
|
||||
|
||||
#### Scenario: 不同系列独立
|
||||
- **WHEN** IoT卡在系列1已触发一次性佣金
|
||||
- **AND** IoT卡首次满足系列2的触发条件
|
||||
- **THEN** 系统发放系列2的一次性佣金(如果规则启用)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 一次性佣金发放对象
|
||||
|
||||
一次性佣金 SHALL 按链式分配规则发放给代理链上的所有相关店铺。
|
||||
|
||||
**变更说明**:从"发放给直接归属店铺"改为"链式分配给代理链上所有店铺"。
|
||||
|
||||
#### Scenario: 链式发放
|
||||
- **WHEN** IoT卡归属代理A1
|
||||
- **AND** 代理链为 平台 → A → A1
|
||||
- **AND** 触发一次性佣金(系列规则返20元)
|
||||
- **AND** A能拿到20元,给A1设置10元
|
||||
- **THEN** A1获得10元
|
||||
- **AND** A获得20-10=10元
|
||||
|
||||
## RENAMED Requirements
|
||||
|
||||
### Requirement: single_recharge 触发类型
|
||||
- **FROM**: `single_recharge`(单次充值)
|
||||
- **TO**: `first_recharge`(首充)
|
||||
@@ -0,0 +1,54 @@
|
||||
# 一次性佣金时效管理
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 时效类型配置
|
||||
|
||||
系统 SHALL 支持三种一次性佣金时效类型:永久(permanent)、固定日期(fixed_date)、相对时长(relative)。
|
||||
|
||||
#### Scenario: 配置永久有效
|
||||
- **WHEN** 配置一次性佣金规则时设置 `validity_type = permanent`
|
||||
- **THEN** 系统 SHALL 保存该配置
|
||||
- **AND** 该规则永久有效,不会过期
|
||||
|
||||
#### Scenario: 配置固定到期日期
|
||||
- **WHEN** 配置一次性佣金规则时设置 `validity_type = fixed_date`
|
||||
- **AND** `validity_value = "2025-12-31"`
|
||||
- **THEN** 系统 SHALL 保存该配置
|
||||
- **AND** 该规则在 2025-12-31 23:59:59 后失效
|
||||
|
||||
#### Scenario: 配置相对时长
|
||||
- **WHEN** 配置一次性佣金规则时设置 `validity_type = relative`
|
||||
- **AND** `validity_value = "3"` (表示 3 个月)
|
||||
- **THEN** 系统 SHALL 保存该配置
|
||||
- **AND** 该规则从创建时间起 3 个月后失效
|
||||
|
||||
### Requirement: 过期规则不触发返佣
|
||||
|
||||
当一次性佣金规则过期时,系统 SHALL 不再触发返佣,即使满足触发条件(首充/累计充值)。
|
||||
|
||||
#### Scenario: 规则过期后首充
|
||||
- **WHEN** 一次性佣金规则已过期
|
||||
- **AND** 客户首充达到阈值
|
||||
- **THEN** 系统 SHALL 不触发一次性佣金
|
||||
- **AND** 正常完成充值和套餐购买
|
||||
|
||||
#### Scenario: 规则有效期内首充
|
||||
- **WHEN** 一次性佣金规则在有效期内
|
||||
- **AND** 客户首充达到阈值
|
||||
- **THEN** 系统 SHALL 触发一次性佣金
|
||||
- **AND** 按链式分配规则分配佣金
|
||||
|
||||
### Requirement: 梯度统计周期与时效一致
|
||||
|
||||
当一次性佣金使用梯度模式时,梯度统计周期(销量/销售额)SHALL 与一次性佣金时效一致。时效结束后统计归零。
|
||||
|
||||
#### Scenario: 时效内统计
|
||||
- **WHEN** 一次性佣金时效为 3 个月
|
||||
- **AND** 使用销量梯度
|
||||
- **THEN** 销量统计 SHALL 只计算这 3 个月内的销量
|
||||
|
||||
#### Scenario: 时效结束后统计重置
|
||||
- **WHEN** 一次性佣金时效到期
|
||||
- **AND** 配置了新的一次性佣金时效
|
||||
- **THEN** 销量/销售额统计 SHALL 从零开始
|
||||
@@ -0,0 +1,74 @@
|
||||
# 套餐管理变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 创建套餐
|
||||
|
||||
系统 SHALL 允许平台管理员创建套餐,包含套餐编码、套餐名称、所属系列、套餐类型、时长、流量配置(真流量必填、虚流量可选)、成本价和建议售价。套餐编码 MUST 全局唯一(排除已删除记录)。新创建的套餐默认为启用状态(1)和下架状态(2)。
|
||||
|
||||
**变更说明**:移除 `price`、`data_type`、`data_amount_mb` 参数,新增 `enable_virtual_data` 参数。
|
||||
|
||||
#### Scenario: 成功创建套餐
|
||||
- **WHEN** 管理员提交有效的套餐信息
|
||||
- **AND** 包含 `cost_price`、`suggested_retail_price`、`real_data_mb`
|
||||
- **THEN** 系统创建套餐记录,状态为启用(1),上架状态为下架(2),返回创建的套餐详情
|
||||
|
||||
#### Scenario: 创建带虚流量的套餐
|
||||
- **WHEN** 管理员提交套餐信息
|
||||
- **AND** `enable_virtual_data = true`
|
||||
- **AND** `virtual_data_mb = 800`
|
||||
- **AND** `real_data_mb = 1000`
|
||||
- **THEN** 系统创建套餐记录,虚流量配置正确保存
|
||||
|
||||
#### Scenario: 套餐编码重复
|
||||
- **WHEN** 管理员提交的套餐编码已存在(未删除)
|
||||
- **THEN** 系统返回错误 "套餐编码已存在"
|
||||
|
||||
#### Scenario: 关联不存在的套餐系列
|
||||
- **WHEN** 管理员指定的系列 ID 不存在
|
||||
- **THEN** 系统返回错误 "套餐系列不存在"
|
||||
|
||||
#### Scenario: 缺少必填字段
|
||||
- **WHEN** 管理员未提供必填字段(套餐编码、套餐名称、套餐类型、时长、成本价、真流量)
|
||||
- **THEN** 系统返回参数验证错误
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Package 模型新增字段
|
||||
|
||||
系统 MUST 在 Package 模型中调整以下字段:
|
||||
- 移除 `price` 字段
|
||||
- 移除 `data_type` 字段
|
||||
- 移除 `data_amount_mb` 字段
|
||||
- 保留 `suggested_cost_price` 并重命名为 `cost_price`:成本价(分为单位)
|
||||
- 保留 `suggested_retail_price`:建议售价(分为单位)
|
||||
- 新增 `enable_virtual_data`:是否启用虚流量,布尔值,默认 false
|
||||
- 保留 `real_data_mb`:真实流量(必填)
|
||||
- 保留 `virtual_data_mb`:虚流量(启用时必填)
|
||||
- 保留 `shelf_status`:上架状态,1-上架 2-下架,默认 2
|
||||
|
||||
#### Scenario: 创建套餐时设置价格
|
||||
- **WHEN** 管理员创建套餐并设置成本价和建议售价
|
||||
- **THEN** 系统保存 `cost_price` 和 `suggested_retail_price`
|
||||
|
||||
#### Scenario: 查询套餐时返回价格
|
||||
- **WHEN** 管理员查询套餐详情或列表
|
||||
- **THEN** 响应中包含 `cost_price`、`suggested_retail_price`、`shelf_status`、`enable_virtual_data` 字段
|
||||
- **AND** 不再返回 `price`、`data_type`、`data_amount_mb` 字段
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: price 字段
|
||||
|
||||
**Reason**: `price` 字段语义不清,与 `suggested_cost_price`、`suggested_retail_price` 混淆
|
||||
**Migration**: 使用 `cost_price`(成本价)和 `suggested_retail_price`(建议售价)替代
|
||||
|
||||
### Requirement: data_type 字段
|
||||
|
||||
**Reason**: `data_type` 暗示真流量/虚流量二选一,但业务需求是共存
|
||||
**Migration**: 使用 `enable_virtual_data` 开关控制是否启用虚流量
|
||||
|
||||
### Requirement: data_amount_mb 字段
|
||||
|
||||
**Reason**: 语义不清,不知道是真流量还是虚流量
|
||||
**Migration**: 使用 `real_data_mb`(真流量)和 `virtual_data_mb`(虚流量)明确区分
|
||||
@@ -0,0 +1,55 @@
|
||||
# 套餐系列管理变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 套餐系列一次性佣金规则配置
|
||||
|
||||
系统 SHALL 在套餐系列层面配置一次性佣金的完整规则,包括触发条件、阈值、金额/梯度、时效、强充配置。
|
||||
|
||||
**变更说明**:一次性佣金规则从分配时配置改为在系列层面统一定义。分配时只设置"给下级多少"。
|
||||
|
||||
#### Scenario: 配置首充规则
|
||||
- **WHEN** 创建或更新套餐系列
|
||||
- **AND** 设置一次性佣金规则:`trigger_type = first_recharge`,`threshold = 10000`(100元),`commission_amount = 2000`(20元)
|
||||
- **THEN** 系统保存该规则配置
|
||||
|
||||
#### Scenario: 配置累计充值规则
|
||||
- **WHEN** 创建或更新套餐系列
|
||||
- **AND** 设置一次性佣金规则:`trigger_type = accumulated_recharge`,`threshold = 20000`(200元),`commission_amount = 4000`(40元)
|
||||
- **THEN** 系统保存该规则配置
|
||||
|
||||
#### Scenario: 配置梯度规则
|
||||
- **WHEN** 创建或更新套餐系列
|
||||
- **AND** 设置一次性佣金规则:`commission_type = tiered`
|
||||
- **AND** 梯度配置:销量>=0返5元,>=100返10元,>=200返20元
|
||||
- **THEN** 系统保存梯度配置
|
||||
|
||||
#### Scenario: 配置时效
|
||||
- **WHEN** 创建或更新套餐系列
|
||||
- **AND** 设置时效:`validity_type = relative`,`validity_value = 3`(3个月)
|
||||
- **THEN** 系统保存时效配置
|
||||
- **AND** 该规则在3个月后失效
|
||||
|
||||
---
|
||||
|
||||
### Requirement: PackageSeries 模型新增字段
|
||||
|
||||
系统 MUST 在 PackageSeries 模型中新增一次性佣金规则配置字段:
|
||||
|
||||
**新增字段**(使用 JSONB 存储):
|
||||
- `one_time_commission_config`:一次性佣金规则配置(JSON)
|
||||
- `enable`:是否启用
|
||||
- `trigger_type`:触发类型(first_recharge / accumulated_recharge)
|
||||
- `threshold`:触发阈值(分)
|
||||
- `commission_type`:返佣类型(fixed / tiered)
|
||||
- `commission_amount`:固定返佣金额(分)
|
||||
- `tiers`:梯度配置数组
|
||||
- `validity_type`:时效类型(permanent / fixed_date / relative)
|
||||
- `validity_value`:时效值
|
||||
- `enable_force_recharge`:是否启用强充
|
||||
- `force_calc_type`:强充金额计算方式(fixed / dynamic)
|
||||
- `force_amount`:强充金额(fixed类型时)
|
||||
|
||||
#### Scenario: 查询系列详情包含规则
|
||||
- **WHEN** 查询套餐系列详情
|
||||
- **THEN** 返回完整的一次性佣金规则配置
|
||||
@@ -0,0 +1,62 @@
|
||||
# 套餐真流量/虚流量共存机制
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 真流量必填
|
||||
|
||||
创建或更新套餐时,系统 SHALL 要求 `real_data_mb`(真实流量额度)为必填字段,且 MUST 大于 0。
|
||||
|
||||
#### Scenario: 创建套餐时提供真流量
|
||||
- **WHEN** 创建套餐请求包含 `real_data_mb = 1000`
|
||||
- **THEN** 系统 SHALL 保存该套餐
|
||||
- **AND** `real_data_mb` 记录为 1000 MB
|
||||
|
||||
#### Scenario: 创建套餐时缺少真流量
|
||||
- **WHEN** 创建套餐请求未提供 `real_data_mb` 字段
|
||||
- **THEN** 系统 SHALL 拒绝请求
|
||||
- **AND** 返回参数验证失败错误
|
||||
|
||||
### Requirement: 虚流量可选开关
|
||||
|
||||
系统 SHALL 提供 `enable_virtual_data` 开关控制是否启用虚流量。启用时 MUST 提供 `virtual_data_mb`,且该值 MUST 小于等于 `real_data_mb`。
|
||||
|
||||
#### Scenario: 启用虚流量
|
||||
- **WHEN** 创建套餐请求包含 `enable_virtual_data = true`
|
||||
- **AND** `virtual_data_mb = 800`
|
||||
- **AND** `real_data_mb = 1000`
|
||||
- **THEN** 系统 SHALL 保存该配置
|
||||
- **AND** `enable_virtual_data` 记录为 true
|
||||
- **AND** `virtual_data_mb` 记录为 800 MB
|
||||
|
||||
#### Scenario: 启用虚流量但未提供额度
|
||||
- **WHEN** 创建套餐请求包含 `enable_virtual_data = true`
|
||||
- **AND** 未提供 `virtual_data_mb`
|
||||
- **THEN** 系统 SHALL 拒绝请求
|
||||
- **AND** 返回"启用虚流量时必须提供虚流量额度"错误
|
||||
|
||||
#### Scenario: 虚流量超过真流量
|
||||
- **WHEN** 创建套餐请求包含 `enable_virtual_data = true`
|
||||
- **AND** `virtual_data_mb = 1200`
|
||||
- **AND** `real_data_mb = 1000`
|
||||
- **THEN** 系统 SHALL 拒绝请求
|
||||
- **AND** 返回"虚流量不能超过真实流量"错误
|
||||
|
||||
#### Scenario: 不启用虚流量
|
||||
- **WHEN** 创建套餐请求包含 `enable_virtual_data = false`
|
||||
- **THEN** 系统 SHALL 保存该配置
|
||||
- **AND** `virtual_data_mb` 可为空或忽略
|
||||
|
||||
### Requirement: 停机判断目标值
|
||||
|
||||
轮询停机模块在判断是否停机时,系统 SHALL 根据 `enable_virtual_data` 选择目标值:启用虚流量时使用 `virtual_data_mb`,否则使用 `real_data_mb`。
|
||||
|
||||
#### Scenario: 启用虚流量的停机判断
|
||||
- **WHEN** 套餐配置 `enable_virtual_data = true`
|
||||
- **AND** `virtual_data_mb = 800`
|
||||
- **AND** `real_data_mb = 1000`
|
||||
- **THEN** 停机判断的目标流量值 SHALL 为 800 MB
|
||||
|
||||
#### Scenario: 未启用虚流量的停机判断
|
||||
- **WHEN** 套餐配置 `enable_virtual_data = false`
|
||||
- **AND** `real_data_mb = 1000`
|
||||
- **THEN** 停机判断的目标流量值 SHALL 为 1000 MB
|
||||
@@ -0,0 +1,53 @@
|
||||
# 店铺佣金梯度变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 梯度统计范围开关
|
||||
|
||||
系统 SHALL 支持配置梯度佣金的统计范围:仅自己(self)或自己+下级(self_and_sub)。
|
||||
|
||||
**变更说明**:新增 `stat_scope` 字段,允许配置统计是否包含下级代理的销量/销售额。
|
||||
|
||||
#### Scenario: 配置统计范围-仅自己
|
||||
- **WHEN** 配置梯度规则时设置 `stat_scope = self`
|
||||
- **THEN** 系统保存该配置
|
||||
- **AND** 计算梯度时只统计该代理直接产生的销量/销售额
|
||||
|
||||
#### Scenario: 配置统计范围-自己+下级
|
||||
- **WHEN** 配置梯度规则时设置 `stat_scope = self_and_sub`
|
||||
- **THEN** 系统保存该配置
|
||||
- **AND** 计算梯度时统计该代理及所有下级代理的销量/销售额之和
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 梯度统计周期
|
||||
|
||||
梯度佣金的统计周期 SHALL 与一次性佣金时效一致。时效结束后统计归零。
|
||||
|
||||
**变更说明**:统计周期从独立配置改为与一次性佣金时效绑定。
|
||||
|
||||
#### Scenario: 时效内统计
|
||||
- **WHEN** 一次性佣金时效为3个月(relative = 3)
|
||||
- **THEN** 销量/销售额统计只计算这3个月内的数据
|
||||
|
||||
#### Scenario: 时效结束统计重置
|
||||
- **WHEN** 一次性佣金时效到期
|
||||
- **AND** 配置了新的时效周期
|
||||
- **THEN** 销量/销售额统计从零开始
|
||||
|
||||
#### Scenario: 永久时效
|
||||
- **WHEN** 一次性佣金时效为永久(permanent)
|
||||
- **THEN** 销量/销售额永久累计,不会重置
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ShopSeriesOneTimeCommissionTier 模型新增字段
|
||||
|
||||
系统 MUST 在 ShopSeriesOneTimeCommissionTier 模型中新增统计范围字段:
|
||||
|
||||
**新增字段**:
|
||||
- `stat_scope`:统计范围,varchar(20),可选值 `self`/`self_and_sub`,默认 `self`
|
||||
|
||||
#### Scenario: 查询梯度配置包含统计范围
|
||||
- **WHEN** 查询梯度配置详情
|
||||
- **THEN** 返回包含 `stat_scope` 字段
|
||||
@@ -0,0 +1,71 @@
|
||||
# 店铺系列分配变更
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 创建店铺系列分配
|
||||
|
||||
系统 SHALL 允许上级店铺为下级店铺分配套餐系列。分配时配置基础返佣和一次性佣金金额(给下级的金额)。
|
||||
|
||||
**变更说明**:移除完整的一次性佣金配置字段(type、trigger、threshold、mode、value 等),改为只配置"给被分配店铺的一次性佣金金额"。一次性佣金规则从套餐系列获取。
|
||||
|
||||
#### Scenario: 创建分配并设置一次性佣金金额
|
||||
- **WHEN** 平台给代理A分配系列
|
||||
- **AND** 系列启用一次性佣金,规则为首充100返20元
|
||||
- **AND** 设置给A的一次性佣金金额为20元
|
||||
- **THEN** 系统创建分配记录
|
||||
- **AND** `one_time_commission_amount` 记录为 2000(分)
|
||||
|
||||
#### Scenario: 设置超额的一次性佣金金额
|
||||
- **WHEN** 代理A给代理A1分配系列
|
||||
- **AND** A自己能拿到的一次性佣金为15元
|
||||
- **AND** 设置给A1的一次性佣金金额为20元
|
||||
- **THEN** 系统拒绝该配置
|
||||
- **AND** 返回错误"给下级的一次性佣金不能超过自己能拿到的金额"
|
||||
|
||||
#### Scenario: 系列未启用一次性佣金
|
||||
- **WHEN** 分配的系列未启用一次性佣金
|
||||
- **THEN** 不需要设置 `one_time_commission_amount`
|
||||
- **AND** 该字段默认为 0
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ShopSeriesAllocation 模型字段调整
|
||||
|
||||
系统 MUST 调整 ShopSeriesAllocation 模型字段:
|
||||
|
||||
**保留字段**:
|
||||
- `shop_id`:被分配的店铺ID
|
||||
- `series_id`:套餐系列ID
|
||||
- `allocator_shop_id`:分配者店铺ID
|
||||
- `base_commission_mode`:基础返佣模式
|
||||
- `base_commission_value`:基础返佣值
|
||||
- `status`:状态
|
||||
|
||||
**新增字段**:
|
||||
- `one_time_commission_amount`:给被分配店铺的一次性佣金金额(分),默认0
|
||||
|
||||
**移除字段**:
|
||||
- `enable_one_time_commission`
|
||||
- `one_time_commission_type`
|
||||
- `one_time_commission_trigger`
|
||||
- `one_time_commission_threshold`
|
||||
- `one_time_commission_mode`
|
||||
- `one_time_commission_value`
|
||||
- `enable_force_recharge`
|
||||
- `force_recharge_amount`
|
||||
- `force_recharge_trigger_type`
|
||||
|
||||
#### Scenario: 查询分配详情
|
||||
- **WHEN** 查询店铺系列分配详情
|
||||
- **THEN** 返回 `one_time_commission_amount`(给该店铺的一次性佣金金额)
|
||||
- **AND** 不再返回完整的一次性佣金配置字段
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: 分配时配置完整一次性佣金规则
|
||||
|
||||
**Reason**: 一次性佣金规则应在套餐系列层面定义,分配时只需设置"给下级多少"
|
||||
**Migration**:
|
||||
1. 一次性佣金规则(触发条件、阈值、金额/梯度)移到套餐系列的配置中
|
||||
2. 分配时只配置 `one_time_commission_amount`(给该代理的金额)
|
||||
3. 迁移脚本将现有 `one_time_commission_value` 迁移到新字段
|
||||
Reference in New Issue
Block a user