Files
junhong_cmp_fiber/openspec/specs/agent-series-grant/spec.md
huang b52cb9a078
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m27s
fix: 修复梯度佣金档位字段缺失,补全授权接口响应字段及强充有效状态
- OneTimeCommissionTierDTO 补充 operator 字段映射
- GrantCommissionTierItem 补充 dimension/stat_scope 字段(从全局配置合并)
- 系列授权列表/详情补充强充锁定状态和强充金额的有效值计算
- 同步 OpenSpec 主规范并归档变更文档
2026-03-05 11:23:28 +08:00

13 KiB
Raw Blame History

Capability: 代理系列授权管理

Purpose

定义"系列授权"Series Grant的 CRUD 操作。系列授权将原本割裂的"系列分配"和"套餐分配"合并为一个原子操作,一次请求完成:授权代理可销售某系列下的指定套餐、设定每个套餐的成本价、配置一次性佣金(固定模式:单值天花板;梯度模式:每档位上限金额列表)和代理自设强充(平台未设时)。

底层仍使用 tb_shop_series_allocationtb_shop_package_allocation 两张表,对外以 ShopSeriesAllocation.ID 作为 grant 主键。


Requirements

Requirement: 创建系列授权(固定模式)

系统 SHALL 提供 POST /shop-series-grants 接口,在一次请求中原子性创建系列分配和套餐分配列表。固定模式下 one_time_commission_amount MUST 必填,且不得超过分配者自身的天花板。

Scenario: 代理成功创建固定模式授权

  • WHEN 代理A自身天花板=80元为直属下级代理B 创建系列授权commission_type=fixedone_time_commission_amount=500050元packages=[{package_id:1, cost_price:3000}, {package_id:2, cost_price:5000}]
  • THEN 系统在事务中创建 1 条 ShopSeriesAllocationone_time_commission_amount=5000和 2 条 ShopPackageAllocation响应返回包含 packages 列表的聚合视图

Scenario: 代理B 已存在此系列授权,重复创建

  • WHEN 代理A 为代理B 创建系列授权但代理B 在此系列下已有 active 授权记录
  • THEN 系统返回错误"该代理已存在此系列授权"

Scenario: 分配者自身无此系列授权

  • WHEN 代理A 自身未被授权此套餐系列尝试为代理B 创建此系列授权
  • THEN 系统返回错误"当前账号无此系列授权,无法向下分配"

Scenario: 平台成功创建固定模式授权

  • WHEN 平台管理员为一级代理创建系列授权commission_type=fixedone_time_commission_amount=800080元系列总额 commission_amount=10000100元
  • THEN 系统创建授权,响应中 allocator_shop_id=0allocator_shop_name="平台"

Scenario: 固定模式 one_time_commission_amount 为必填

  • WHEN 请求中不包含 one_time_commission_amount
  • THEN 系统返回参数错误"固定模式下一次性佣金额度为必填项"

Scenario: 金额超过代理自身天花板

  • WHEN 代理A天花板=8000分为代理B 创建授权one_time_commission_amount=10000
  • THEN 系统返回错误"一次性佣金额度不能超过上级限额"

Scenario: 平台金额超过系列总额

  • WHEN 平台为代理A 创建授权one_time_commission_amount=12000但 PackageSeries.commission_amount=10000
  • THEN 系统返回错误"一次性佣金额度不能超过套餐系列设定的总额"

Requirement: 创建系列授权(梯度模式)

系统 SHALL 支持梯度模式的系列授权创建。梯度模式下,commission_tiers MUST 为必填,且必须包含与 PackageSeries 完全相同数量和阈值的阶梯(不多不少)。若某档位不希望给下级佣金,应将该档位的 amount 设为 0不可省略该档位。创建成功后的响应中commission_tiers 每个档位 MUST 包含 operatordimensionstat_scope 字段(从全局配置合并)。

Scenario: 代理成功创建梯度模式授权

  • WHEN 代理A 的专属阶梯为 [{operator:">=" , threshold:100, amount:80}, {operator:">=" , threshold:150, amount:120}]A 为代理B 创建授权,传入 commission_tiers=[{threshold:100, amount:50}, {threshold:150, amount:100}]
  • THEN 系统创建授权,commission_tiers_json 存储 [{threshold:100, amount:50}, {threshold:150, amount:100}]
  • AND 响应中 commission_tiers=[{operator:">=" , dimension:"sales_count", stat_scope:"self", threshold:100, amount:50}, ...]

Scenario: 平台成功创建梯度模式授权

  • WHEN 平台为顶级代理A 创建授权PackageSeries 阶梯含 operator/dimension/stat_scope
  • THEN 系统创建授权,响应中 commission_tiers 包含 PackageSeries 全局 operatordimensionstat_scope

Scenario: 梯度模式某档位金额超过父级

  • WHEN 代理A 的阶梯第一档 amount=80A 为 B 创建授权时传入第一档 amount=90
  • THEN 系统返回错误“某档位佣金金额超过上级天花板”

Scenario: 梯度模式传入了不存在的阈值

  • WHEN PackageSeries 只有 threshold=100150 两档,请求中传入 threshold=200
  • THEN 系统返回错误“梯度阶梯 threshold 与系列配置不匹配”

Scenario: 梯度模式 commission_tiers 为必填

  • WHEN 请求中不包含 commission_tiers 或为空数组
  • THEN 系统返回参数错误“梯度模式必须填写阶梯配置”

Requirement: 强充配置的平台/代理层级

创建系列授权时,系统 SHALL 根据 PackageSeries 的触发类型和强充设置决定代理是否可自设强充。

Scenario: 首次充值触发类型,强充不可配置

  • WHEN PackageSeries.trigger_type=first_recharge代理创建授权时传入任意 enable_force_recharge 值
  • THEN 系统忽略代理的强充设置,响应中 force_recharge_locked=true首次充值本身即为强充机制无需额外配置

Scenario: 累计充值触发类型,平台已设强充,代理配置被忽略

  • WHEN PackageSeries.trigger_type=accumulated_recharge 且 enable_force_recharge=true代理创建授权时传入 enable_force_recharge=false
  • THEN 系统忽略代理的强充设置,响应中 force_recharge_locked=true

Scenario: 累计充值触发类型,平台未设强充,代理可自设

  • WHEN PackageSeries.trigger_type=accumulated_recharge 且 enable_force_recharge=false代理创建授权时传入 enable_force_recharge=trueforce_recharge_amount=10000
  • THEN 系统保存代理的强充配置,响应中 force_recharge_locked=falseforce_recharge_enabled=trueforce_recharge_amount=10000

Requirement: 查询系列授权详情

系统 SHALL 提供 GET /shop-series-grants/:id 接口,返回包含套餐列表的聚合视图。梯度模式下,commission_tiers 中每个档位 MUST 包含 dimension(统计维度)和 stat_scope(统计范围)字段,这两个字段从 PackageSeries 全局配置按 threshold 合并,对代理只读。

Scenario: 固定模式详情

  • WHEN 查询固定模式系列授权详情
  • THEN 响应包含 commission_type="fixed"one_time_commission_amount=有效值commission_tiers=[]

Scenario: 梯度模式详情

  • WHEN 查询梯度模式系列授权详情
  • THEN 响应包含 commission_type="tiered"one_time_commission_amount=0
  • AND commission_tiers 中每个档位包含 operatordimensionstat_scopethresholdamount 五个字段
  • AND operatordimensionstat_scope 的值来自 PackageSeries 全局配置(对应 threshold 的档位),代理的 amount 来自 ShopSeriesAllocation.commission_tiers_json

Scenario: 梯度模式 dimension 为销售量

  • WHEN 查询梯度模式授权详情PackageSeries 阶梯 dimension = "sales_count"
  • THEN 响应中对应档位 dimension = "sales_count",前端展示“销售量”条件

Scenario: 梯度模式 dimension 为销售额

  • WHEN 查询梯度模式授权详情PackageSeries 阶梯 dimension = "sales_amount"
  • THEN 响应中对应档位 dimension = "sales_amount",前端展示“销售额”条件

Scenario: 梯度模式 stat_scope 区分

  • WHEN 查询梯度模式授权详情
  • THEN 响应中 stat_scope 正确反映 PackageSeries 配置的统计范围("self""self_and_sub"

Scenario: 查询不存在的授权

  • WHEN 查询不存在的授权 ID
  • THEN 系统返回错误“授权记录不存在”

Requirement: 查询系列授权列表

系统 SHALL 提供 GET /shop-series-grants 接口,支持分页和多维度筛选,响应内嵌套餐数量摘要(不含完整套餐列表)。

Scenario: 列表查询支持按店铺和系列筛选

  • WHEN 传入 shop_id、series_id、allocator_shop_id 等筛选条件
  • THEN 仅返回符合条件的授权记录,每条记录包含 package_count

Requirement: 更新系列授权配置

系统 SHALL 提供 PUT /shop-series-grants/:id 接口,支持更新一次性佣金配置和强充配置。

Scenario: 固定模式更新佣金额度

  • WHEN 更新 one_time_commission_amount新值不超过分配者天花板
  • THEN 系统更新成功

Scenario: 梯度模式更新阶梯金额

  • WHEN 更新 commission_tiers每档位金额不超过分配者同档位上限
  • THEN 系统更新 commission_tiers_json 字段

Scenario: 更新代理自设强充(平台未设时)

  • WHEN 平台未设强充,更新 enable_force_recharge=trueforce_recharge_amount=10000
  • THEN 系统更新成功,后续该代理渠道下的客户须满足强充要求

Requirement: 管理授权内套餐

系统 SHALL 提供 PUT /shop-series-grants/:id/packages 接口,支持添加套餐、移除套餐、更新成本价,操作在事务中完成,成功后返回 HTTP 200无需返回完整授权视图

Scenario: 向授权中添加新套餐

  • WHEN 请求包含新的 package_id 和 cost_price且该套餐属于此系列
  • THEN 系统创建新的 ShopPackageAllocation

Scenario: 更新套餐成本价

  • WHEN 请求中套餐的 cost_price 与当前值不同
  • THEN 系统更新 cost_price 并写价格历史记录

Scenario: 移除授权中的套餐

  • WHEN 请求中某套餐标记 remove=true且该套餐在当前授权中存在
  • THEN 系统软删除对应的 ShopPackageAllocation

Scenario: remove=true 但套餐已不在授权中

  • WHEN 请求中某套餐标记 remove=true但该套餐已被软删除或从未在此授权中
  • THEN 系统静默忽略该条目,不报错,继续处理其他条目

Scenario: 重新添加曾被移除的套餐

  • WHEN 某套餐曾经被软删除,请求中再次包含该 package_id无 remove 标志)
  • THEN 系统创建一条新的 ShopPackageAllocation 记录,不恢复旧记录

Scenario: 添加不属于该系列的套餐

  • WHEN 请求中包含不属于该系列的 package_id
  • THEN 系统返回错误"套餐不属于该系列,无法添加到此授权"

Scenario: 添加上级未授权的套餐

  • WHEN 代理A 尝试添加代理A 自己也未获授权的套餐
  • THEN 系统返回错误"无权限分配该套餐"

Requirement: 删除系列授权

系统 SHALL 提供 DELETE /shop-series-grants/:id 接口,删除时同步软删除所有关联的套餐分配。

Scenario: 成功删除无下级依赖的授权

  • WHEN 删除一个下级代理未基于此授权再分配的记录
  • THEN 系统软删除 ShopSeriesAllocation 和所有关联的 ShopPackageAllocation

Scenario: 有下级依赖时禁止删除

  • WHEN 删除一个已被下级代理用于创建子授权的记录
  • THEN 系统返回错误"存在下级依赖,无法删除,请先删除下级授权"

Requirement: 系列授权列表强充状态正确反映

系列授权列表 (GET /shop-series-grants) MUST 在每个列表项中返回 force_recharge_locked(是否被套餐系列锁定)和 force_recharge_amount(强充金额)。force_recharge_enabled MUST 反映有效状态:当锁定时为 true(无论分配记录自身如何);未锁定时取分配记录的实际设置。

Scenario: 套餐系列锁定强充

  • WHEN 套餐系列配置 enable_force_recharge=truetrigger_type=first_recharge,查询列表
  • THEN 列表项中 force_recharge_locked=trueforce_recharge_enabled=trueforce_recharge_amount=系列配置的 force_amount

Scenario: 代理自身开启强充(未锁定)

  • WHEN 套餐系列未锁定强充,分配记录中 enable_force_recharge=true,查询列表
  • THEN 列表项中 force_recharge_locked=falseforce_recharge_enabled=trueforce_recharge_amount=分配记录的实际金额

Scenario: 代理未开启强充(未锁定)

  • WHEN 套餐系列未锁定强充,分配记录中 enable_force_recharge=false,查询列表
  • THEN 列表项中 force_recharge_locked=falseforce_recharge_enabled=falseforce_recharge_amount=0

Requirement: 查询系列授权详情强充有效状态

系列授权详情 (GET /shop-series-grants/:id) 中,force_recharge_enabled MUST 反映有效状态:当锁定时为 trueforce_recharge_amount 锁定时应返回系列配置的 force_amount

Scenario: 锁定强充时详情响应

  • WHEN 套餐系列锁定强充,查询对应分配记录详情
  • THEN force_recharge_locked=trueforce_recharge_enabled=trueforce_recharge_amount=系列配置的 force_amount