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:
@@ -74,6 +74,9 @@ components:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
enterprise_name:
|
||||
description: 企业名称
|
||||
type: string
|
||||
id:
|
||||
description: 账号ID
|
||||
minimum: 0
|
||||
@@ -86,6 +89,9 @@ components:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
shop_name:
|
||||
description: 店铺名称
|
||||
type: string
|
||||
status:
|
||||
description: 状态 (0:禁用, 1:启用)
|
||||
type: integer
|
||||
@@ -632,23 +638,13 @@ components:
|
||||
description: 设备号
|
||||
type: string
|
||||
type: object
|
||||
DtoBaseCommissionConfig:
|
||||
properties:
|
||||
mode:
|
||||
description: 返佣模式 (fixed:固定金额, percent:百分比)
|
||||
type: string
|
||||
value:
|
||||
description: 返佣值(分或千分比,如200=20%)
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- mode
|
||||
- value
|
||||
type: object
|
||||
DtoBatchAllocatePackagesRequest:
|
||||
properties:
|
||||
base_commission:
|
||||
$ref: '#/components/schemas/DtoBaseCommissionConfig'
|
||||
one_time_commission_amount:
|
||||
description: 该代理能拿到的一次性佣金(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
price_adjustment:
|
||||
$ref: '#/components/schemas/DtoPriceAdjustment'
|
||||
series_id:
|
||||
@@ -662,7 +658,6 @@ components:
|
||||
required:
|
||||
- shop_id
|
||||
- series_id
|
||||
- base_commission
|
||||
type: object
|
||||
DtoBatchSetCardSeriesBindngRequest:
|
||||
properties:
|
||||
@@ -1119,20 +1114,18 @@ components:
|
||||
type: object
|
||||
DtoCreatePackageRequest:
|
||||
properties:
|
||||
data_amount_mb:
|
||||
description: 总流量额度(MB)
|
||||
cost_price:
|
||||
description: 成本价(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
data_type:
|
||||
description: 流量类型 (real:真流量, virtual:虚流量)
|
||||
nullable: true
|
||||
type: string
|
||||
duration_months:
|
||||
description: 套餐时长(月数)
|
||||
maximum: 120
|
||||
minimum: 1
|
||||
type: integer
|
||||
enable_virtual_data:
|
||||
description: 是否启用虚流量
|
||||
type: boolean
|
||||
package_code:
|
||||
description: 套餐编码
|
||||
maxLength: 100
|
||||
@@ -1146,10 +1139,6 @@ components:
|
||||
package_type:
|
||||
description: 套餐类型 (formal:正式套餐, addon:附加套餐)
|
||||
type: string
|
||||
price:
|
||||
description: 套餐价格(分)
|
||||
minimum: 0
|
||||
type: integer
|
||||
real_data_mb:
|
||||
description: 真流量额度(MB)
|
||||
minimum: 0
|
||||
@@ -1160,11 +1149,6 @@ components:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
suggested_cost_price:
|
||||
description: 建议成本价(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
suggested_retail_price:
|
||||
description: 建议售价(分)
|
||||
minimum: 0
|
||||
@@ -1180,7 +1164,7 @@ components:
|
||||
- package_name
|
||||
- package_type
|
||||
- duration_months
|
||||
- price
|
||||
- cost_price
|
||||
type: object
|
||||
DtoCreatePackageSeriesRequest:
|
||||
properties:
|
||||
@@ -1188,6 +1172,8 @@ components:
|
||||
description: 描述
|
||||
maxLength: 500
|
||||
type: string
|
||||
one_time_commission_config:
|
||||
$ref: '#/components/schemas/DtoSeriesOneTimeCommissionConfigDTO'
|
||||
series_code:
|
||||
description: 系列编码
|
||||
maxLength: 100
|
||||
@@ -1279,7 +1265,7 @@ components:
|
||||
DtoCreateShopPackageAllocationRequest:
|
||||
properties:
|
||||
cost_price:
|
||||
description: 覆盖的成本价(分)
|
||||
description: 该代理的成本价(分)
|
||||
minimum: 0
|
||||
type: integer
|
||||
package_id:
|
||||
@@ -1361,25 +1347,35 @@ components:
|
||||
type: object
|
||||
DtoCreateShopSeriesAllocationRequest:
|
||||
properties:
|
||||
base_commission:
|
||||
$ref: '#/components/schemas/DtoBaseCommissionConfig'
|
||||
enable_force_recharge:
|
||||
description: 是否启用强充(累计充值强充)
|
||||
description: 是否启用强制充值
|
||||
nullable: true
|
||||
type: boolean
|
||||
enable_one_time_commission:
|
||||
description: 是否启用一次性佣金
|
||||
nullable: true
|
||||
type: boolean
|
||||
force_recharge_amount:
|
||||
description: 强充金额(分,0表示使用阈值金额)
|
||||
description: 强制充值金额(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
force_recharge_trigger_type:
|
||||
description: 强充触发类型(1:单次充值, 2:累计充值)
|
||||
description: 强充触发类型 (1:单次充值, 2:累计充值)
|
||||
nullable: true
|
||||
type: integer
|
||||
one_time_commission_config:
|
||||
$ref: '#/components/schemas/DtoOneTimeCommissionConfig'
|
||||
one_time_commission_amount:
|
||||
description: 该代理能拿的一次性佣金金额上限(分)
|
||||
minimum: 0
|
||||
type: integer
|
||||
one_time_commission_threshold:
|
||||
description: 一次性佣金触发阈值(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
one_time_commission_trigger:
|
||||
description: 一次性佣金触发类型 (first_recharge:首次充值, accumulated_recharge:累计充值)
|
||||
type: string
|
||||
series_id:
|
||||
description: 套餐系列ID
|
||||
minimum: 0
|
||||
@@ -1391,7 +1387,7 @@ components:
|
||||
required:
|
||||
- shop_id
|
||||
- series_id
|
||||
- base_commission
|
||||
- one_time_commission_amount
|
||||
type: object
|
||||
DtoCreateWithdrawalSettingReq:
|
||||
properties:
|
||||
@@ -2205,9 +2201,6 @@ components:
|
||||
card_category:
|
||||
description: 卡业务类型 (normal:普通卡, industry:行业卡)
|
||||
type: string
|
||||
card_type:
|
||||
description: 卡类型
|
||||
type: string
|
||||
carrier_id:
|
||||
description: 运营商ID
|
||||
minimum: 0
|
||||
@@ -2530,57 +2523,26 @@ components:
|
||||
description: 已提现佣金(分)
|
||||
type: integer
|
||||
type: object
|
||||
DtoOneTimeCommissionConfig:
|
||||
DtoOneTimeCommissionTierDTO:
|
||||
properties:
|
||||
mode:
|
||||
description: 返佣模式 (fixed:固定金额, percent:百分比) - 固定类型时必填
|
||||
amount:
|
||||
description: 佣金金额(分)
|
||||
minimum: 0
|
||||
type: integer
|
||||
dimension:
|
||||
description: 统计维度 (sales_count:销量, sales_amount:销售额)
|
||||
type: string
|
||||
stat_scope:
|
||||
description: 统计范围 (self:仅自己, self_and_sub:自己+下级)
|
||||
type: string
|
||||
threshold:
|
||||
description: 最低阈值(分)
|
||||
minimum: 1
|
||||
type: integer
|
||||
tiers:
|
||||
description: 梯度档位列表 - 梯度类型时必填
|
||||
items:
|
||||
$ref: '#/components/schemas/DtoOneTimeCommissionTierEntry'
|
||||
nullable: true
|
||||
type: array
|
||||
trigger:
|
||||
description: 触发条件 (single_recharge:单次充值, accumulated_recharge:累计充值)
|
||||
type: string
|
||||
type:
|
||||
description: 一次性佣金类型 (fixed:固定, tiered:梯度)
|
||||
type: string
|
||||
value:
|
||||
description: 佣金金额(分)或比例(千分比)- 固定类型时必填
|
||||
minimum: 1
|
||||
description: 达标阈值
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- type
|
||||
- trigger
|
||||
- dimension
|
||||
- threshold
|
||||
type: object
|
||||
DtoOneTimeCommissionTierEntry:
|
||||
properties:
|
||||
mode:
|
||||
description: 返佣模式 (fixed:固定金额, percent:百分比)
|
||||
type: string
|
||||
threshold:
|
||||
description: 梯度阈值(销量或销售额分)
|
||||
minimum: 1
|
||||
type: integer
|
||||
tier_type:
|
||||
description: 梯度类型 (sales_count:销量, sales_amount:销售额)
|
||||
type: string
|
||||
value:
|
||||
description: 返佣值(分或千分比)
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- tier_type
|
||||
- threshold
|
||||
- mode
|
||||
- value
|
||||
- amount
|
||||
type: object
|
||||
DtoOrderItemResponse:
|
||||
properties:
|
||||
@@ -2720,8 +2682,7 @@ components:
|
||||
DtoPackageResponse:
|
||||
properties:
|
||||
cost_price:
|
||||
description: 成本价(分,仅代理用户可见)
|
||||
nullable: true
|
||||
description: 成本价(分)
|
||||
type: integer
|
||||
created_at:
|
||||
description: 创建时间
|
||||
@@ -2729,19 +2690,20 @@ components:
|
||||
current_commission_rate:
|
||||
description: 当前返佣比例(仅代理用户可见)
|
||||
type: string
|
||||
data_amount_mb:
|
||||
description: 总流量额度(MB)
|
||||
type: integer
|
||||
data_type:
|
||||
description: 流量类型 (real:真流量, virtual:虚流量)
|
||||
type: string
|
||||
duration_months:
|
||||
description: 套餐时长(月数)
|
||||
type: integer
|
||||
enable_virtual_data:
|
||||
description: 是否启用虚流量
|
||||
type: boolean
|
||||
id:
|
||||
description: 套餐ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
one_time_commission_amount:
|
||||
description: 一次性佣金金额(分,代理视角)
|
||||
nullable: true
|
||||
type: integer
|
||||
package_code:
|
||||
description: 套餐编码
|
||||
type: string
|
||||
@@ -2751,9 +2713,6 @@ components:
|
||||
package_type:
|
||||
description: 套餐类型 (formal:正式套餐, addon:附加套餐)
|
||||
type: string
|
||||
price:
|
||||
description: 套餐价格(分)
|
||||
type: integer
|
||||
profit_margin:
|
||||
description: 利润空间(分,仅代理用户可见)
|
||||
nullable: true
|
||||
@@ -2776,9 +2735,6 @@ components:
|
||||
status:
|
||||
description: 状态 (1:启用, 2:禁用)
|
||||
type: integer
|
||||
suggested_cost_price:
|
||||
description: 建议成本价(分)
|
||||
type: integer
|
||||
suggested_retail_price:
|
||||
description: 建议售价(分)
|
||||
type: integer
|
||||
@@ -2820,10 +2776,15 @@ components:
|
||||
description:
|
||||
description: 描述
|
||||
type: string
|
||||
enable_one_time_commission:
|
||||
description: 是否启用一次性佣金
|
||||
type: boolean
|
||||
id:
|
||||
description: 系列ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
one_time_commission_config:
|
||||
$ref: '#/components/schemas/DtoSeriesOneTimeCommissionConfigDTO'
|
||||
series_code:
|
||||
description: 系列编码
|
||||
type: string
|
||||
@@ -3380,6 +3341,48 @@ components:
|
||||
minimum: 0
|
||||
type: integer
|
||||
type: object
|
||||
DtoSeriesOneTimeCommissionConfigDTO:
|
||||
properties:
|
||||
commission_amount:
|
||||
description: 固定佣金金额(分),commission_type=fixed时使用
|
||||
minimum: 0
|
||||
type: integer
|
||||
commission_type:
|
||||
description: 佣金类型 (fixed:固定, tiered:梯度)
|
||||
type: string
|
||||
enable:
|
||||
description: 是否启用一次性佣金
|
||||
type: boolean
|
||||
enable_force_recharge:
|
||||
description: 是否启用强充
|
||||
type: boolean
|
||||
force_amount:
|
||||
description: 强充金额(分)
|
||||
minimum: 0
|
||||
type: integer
|
||||
force_calc_type:
|
||||
description: 强充计算类型 (fixed:固定, dynamic:动态)
|
||||
type: string
|
||||
threshold:
|
||||
description: 触发阈值(分)
|
||||
minimum: 0
|
||||
type: integer
|
||||
tiers:
|
||||
description: 梯度配置列表,commission_type=tiered时使用
|
||||
items:
|
||||
$ref: '#/components/schemas/DtoOneTimeCommissionTierDTO'
|
||||
nullable: true
|
||||
type: array
|
||||
trigger_type:
|
||||
description: 触发类型 (first_recharge:首充, accumulated_recharge:累计充值)
|
||||
type: string
|
||||
validity_type:
|
||||
description: 时效类型 (permanent:永久, fixed_date:固定日期, relative:相对时长)
|
||||
type: string
|
||||
validity_value:
|
||||
description: 时效值(日期或月数)
|
||||
type: string
|
||||
type: object
|
||||
DtoSetSpeedLimitRequest:
|
||||
properties:
|
||||
download_speed:
|
||||
@@ -3554,15 +3557,15 @@ components:
|
||||
type: object
|
||||
DtoShopPackageAllocationResponse:
|
||||
properties:
|
||||
allocation_id:
|
||||
description: 关联的系列分配ID
|
||||
allocator_shop_id:
|
||||
description: 分配者店铺ID,0表示平台分配
|
||||
minimum: 0
|
||||
type: integer
|
||||
calculated_cost_price:
|
||||
description: 原计算成本价(分),供参考
|
||||
type: integer
|
||||
allocator_shop_name:
|
||||
description: 分配者店铺名称
|
||||
type: string
|
||||
cost_price:
|
||||
description: 覆盖的成本价(分)
|
||||
description: 该代理的成本价(分)
|
||||
type: integer
|
||||
created_at:
|
||||
description: 创建时间
|
||||
@@ -3581,6 +3584,18 @@ components:
|
||||
package_name:
|
||||
description: 套餐名称
|
||||
type: string
|
||||
series_allocation_id:
|
||||
description: 关联的系列分配ID
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
series_id:
|
||||
description: 套餐系列ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
series_name:
|
||||
description: 套餐系列名称
|
||||
type: string
|
||||
shop_id:
|
||||
description: 被分配的店铺ID
|
||||
minimum: 0
|
||||
@@ -3718,35 +3733,43 @@ components:
|
||||
DtoShopSeriesAllocationResponse:
|
||||
properties:
|
||||
allocator_shop_id:
|
||||
description: 分配者店铺ID
|
||||
description: 分配者店铺ID,0表示平台分配
|
||||
minimum: 0
|
||||
type: integer
|
||||
allocator_shop_name:
|
||||
description: 分配者店铺名称
|
||||
type: string
|
||||
base_commission:
|
||||
$ref: '#/components/schemas/DtoBaseCommissionConfig'
|
||||
created_at:
|
||||
description: 创建时间
|
||||
type: string
|
||||
enable_force_recharge:
|
||||
description: 是否启用强充
|
||||
description: 是否启用强制充值
|
||||
type: boolean
|
||||
enable_one_time_commission:
|
||||
description: 是否启用一次性佣金
|
||||
type: boolean
|
||||
force_recharge_amount:
|
||||
description: 强充金额(分)
|
||||
description: 强制充值金额(分)
|
||||
type: integer
|
||||
force_recharge_trigger_type:
|
||||
description: 强充触发类型(1:单次充值, 2:累计充值)
|
||||
description: 强充触发类型 (1:单次充值, 2:累计充值)
|
||||
type: integer
|
||||
id:
|
||||
description: 分配ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
one_time_commission_config:
|
||||
$ref: '#/components/schemas/DtoOneTimeCommissionConfig'
|
||||
one_time_commission_amount:
|
||||
description: 该代理能拿的一次性佣金金额上限(分)
|
||||
type: integer
|
||||
one_time_commission_threshold:
|
||||
description: 一次性佣金触发阈值(分)
|
||||
type: integer
|
||||
one_time_commission_trigger:
|
||||
description: 一次性佣金触发类型
|
||||
type: string
|
||||
series_code:
|
||||
description: 套餐系列编码
|
||||
type: string
|
||||
series_id:
|
||||
description: 套餐系列ID
|
||||
minimum: 0
|
||||
@@ -3888,9 +3911,6 @@ components:
|
||||
card_category:
|
||||
description: 卡业务类型 (normal:普通卡, industry:行业卡)
|
||||
type: string
|
||||
card_type:
|
||||
description: 卡类型
|
||||
type: string
|
||||
carrier_id:
|
||||
description: 运营商ID
|
||||
minimum: 0
|
||||
@@ -4110,21 +4130,21 @@ components:
|
||||
type: object
|
||||
DtoUpdatePackageParams:
|
||||
properties:
|
||||
data_amount_mb:
|
||||
description: 总流量额度(MB)
|
||||
cost_price:
|
||||
description: 成本价(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
data_type:
|
||||
description: 流量类型 (real:真流量, virtual:虚流量)
|
||||
nullable: true
|
||||
type: string
|
||||
duration_months:
|
||||
description: 套餐时长(月数)
|
||||
maximum: 120
|
||||
minimum: 1
|
||||
nullable: true
|
||||
type: integer
|
||||
enable_virtual_data:
|
||||
description: 是否启用虚流量
|
||||
nullable: true
|
||||
type: boolean
|
||||
package_name:
|
||||
description: 套餐名称
|
||||
maxLength: 255
|
||||
@@ -4135,11 +4155,6 @@ components:
|
||||
description: 套餐类型 (formal:正式套餐, addon:附加套餐)
|
||||
nullable: true
|
||||
type: string
|
||||
price:
|
||||
description: 套餐价格(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
real_data_mb:
|
||||
description: 真流量额度(MB)
|
||||
minimum: 0
|
||||
@@ -4150,11 +4165,6 @@ components:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
suggested_cost_price:
|
||||
description: 建议成本价(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
suggested_retail_price:
|
||||
description: 建议售价(分)
|
||||
minimum: 0
|
||||
@@ -4173,6 +4183,8 @@ components:
|
||||
maxLength: 500
|
||||
nullable: true
|
||||
type: string
|
||||
one_time_commission_config:
|
||||
$ref: '#/components/schemas/DtoSeriesOneTimeCommissionConfigDTO'
|
||||
series_name:
|
||||
description: 系列名称
|
||||
maxLength: 255
|
||||
@@ -4278,16 +4290,13 @@ components:
|
||||
properties:
|
||||
status:
|
||||
description: 状态 (0:禁用, 1:启用)
|
||||
maximum: 1
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
DtoUpdateShopPackageAllocationParams:
|
||||
properties:
|
||||
cost_price:
|
||||
description: 覆盖的成本价(分)
|
||||
description: 该代理的成本价(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
@@ -4333,10 +4342,8 @@ components:
|
||||
type: object
|
||||
DtoUpdateShopSeriesAllocationParams:
|
||||
properties:
|
||||
base_commission:
|
||||
$ref: '#/components/schemas/DtoBaseCommissionConfig'
|
||||
enable_force_recharge:
|
||||
description: 是否启用强充(累计充值强充)
|
||||
description: 是否启用强制充值
|
||||
nullable: true
|
||||
type: boolean
|
||||
enable_one_time_commission:
|
||||
@@ -4344,15 +4351,32 @@ components:
|
||||
nullable: true
|
||||
type: boolean
|
||||
force_recharge_amount:
|
||||
description: 强充金额(分,0表示使用阈值金额)
|
||||
description: 强制充值金额(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
force_recharge_trigger_type:
|
||||
description: 强充触发类型(1:单次充值, 2:累计充值)
|
||||
description: 强充触发类型 (1:单次充值, 2:累计充值)
|
||||
nullable: true
|
||||
type: integer
|
||||
one_time_commission_amount:
|
||||
description: 该代理能拿的一次性佣金金额上限(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
one_time_commission_threshold:
|
||||
description: 一次性佣金触发阈值(分)
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
one_time_commission_trigger:
|
||||
description: 一次性佣金触发类型
|
||||
nullable: true
|
||||
type: string
|
||||
status:
|
||||
description: 状态 (1:启用, 2:禁用)
|
||||
nullable: true
|
||||
type: integer
|
||||
one_time_commission_config:
|
||||
$ref: '#/components/schemas/DtoOneTimeCommissionConfig'
|
||||
type: object
|
||||
DtoUpdateStatusParams:
|
||||
properties:
|
||||
@@ -4853,6 +4877,22 @@ paths:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 店铺ID筛选
|
||||
in: query
|
||||
name: shop_id
|
||||
schema:
|
||||
description: 店铺ID筛选
|
||||
minimum: 1
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 企业ID筛选
|
||||
in: query
|
||||
name: enterprise_id
|
||||
schema:
|
||||
description: 企业ID筛选
|
||||
minimum: 1
|
||||
nullable: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
@@ -11008,6 +11048,13 @@ paths:
|
||||
description: 状态 (1:启用, 2:禁用)
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 是否启用一次性佣金
|
||||
in: query
|
||||
name: enable_one_time_commission
|
||||
schema:
|
||||
description: 是否启用一次性佣金
|
||||
nullable: true
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
@@ -12823,6 +12870,22 @@ paths:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 系列分配ID
|
||||
in: query
|
||||
name: series_allocation_id
|
||||
schema:
|
||||
description: 系列分配ID
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 分配者店铺ID
|
||||
in: query
|
||||
name: allocator_shop_id
|
||||
schema:
|
||||
description: 分配者店铺ID
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 状态 (1:启用, 2:禁用)
|
||||
in: query
|
||||
name: status
|
||||
@@ -12885,7 +12948,7 @@ paths:
|
||||
- BearerAuth: []
|
||||
summary: 单套餐分配列表
|
||||
tags:
|
||||
- 单套餐分配
|
||||
- 套餐分配
|
||||
post:
|
||||
requestBody:
|
||||
content:
|
||||
@@ -12947,7 +13010,7 @@ paths:
|
||||
- BearerAuth: []
|
||||
summary: 创建单套餐分配
|
||||
tags:
|
||||
- 单套餐分配
|
||||
- 套餐分配
|
||||
/api/admin/shop-package-allocations/{id}:
|
||||
delete:
|
||||
parameters:
|
||||
@@ -12988,7 +13051,7 @@ paths:
|
||||
- BearerAuth: []
|
||||
summary: 删除单套餐分配
|
||||
tags:
|
||||
- 单套餐分配
|
||||
- 套餐分配
|
||||
get:
|
||||
parameters:
|
||||
- description: ID
|
||||
@@ -13054,7 +13117,7 @@ paths:
|
||||
- BearerAuth: []
|
||||
summary: 获取单套餐分配详情
|
||||
tags:
|
||||
- 单套餐分配
|
||||
- 套餐分配
|
||||
put:
|
||||
parameters:
|
||||
- description: ID
|
||||
@@ -13125,7 +13188,7 @@ paths:
|
||||
- BearerAuth: []
|
||||
summary: 更新单套餐分配
|
||||
tags:
|
||||
- 单套餐分配
|
||||
- 套餐分配
|
||||
/api/admin/shop-package-allocations/{id}/cost-price:
|
||||
put:
|
||||
parameters:
|
||||
@@ -13192,7 +13255,7 @@ paths:
|
||||
- BearerAuth: []
|
||||
summary: 更新单套餐分配成本价
|
||||
tags:
|
||||
- 单套餐分配
|
||||
- 套餐分配
|
||||
/api/admin/shop-package-allocations/{id}/status:
|
||||
put:
|
||||
parameters:
|
||||
@@ -13238,7 +13301,7 @@ paths:
|
||||
- BearerAuth: []
|
||||
summary: 更新单套餐分配状态
|
||||
tags:
|
||||
- 单套餐分配
|
||||
- 套餐分配
|
||||
/api/admin/shop-package-batch-allocations:
|
||||
post:
|
||||
requestBody:
|
||||
@@ -13373,6 +13436,14 @@ paths:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 分配者店铺ID
|
||||
in: query
|
||||
name: allocator_shop_id
|
||||
schema:
|
||||
description: 分配者店铺ID
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 状态 (1:启用, 2:禁用)
|
||||
in: query
|
||||
name: status
|
||||
@@ -13433,9 +13504,9 @@ paths:
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 套餐系列分配列表
|
||||
summary: 系列分配列表
|
||||
tags:
|
||||
- 套餐系列分配
|
||||
- 套餐分配
|
||||
post:
|
||||
requestBody:
|
||||
content:
|
||||
@@ -13495,9 +13566,9 @@ paths:
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 创建套餐系列分配
|
||||
summary: 创建系列分配
|
||||
tags:
|
||||
- 套餐系列分配
|
||||
- 套餐分配
|
||||
/api/admin/shop-series-allocations/{id}:
|
||||
delete:
|
||||
parameters:
|
||||
@@ -13536,9 +13607,9 @@ paths:
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 删除套餐系列分配
|
||||
summary: 删除系列分配
|
||||
tags:
|
||||
- 套餐系列分配
|
||||
- 套餐分配
|
||||
get:
|
||||
parameters:
|
||||
- description: ID
|
||||
@@ -13602,9 +13673,9 @@ paths:
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 获取套餐系列分配详情
|
||||
summary: 获取系列分配详情
|
||||
tags:
|
||||
- 套餐系列分配
|
||||
- 套餐分配
|
||||
put:
|
||||
parameters:
|
||||
- description: ID
|
||||
@@ -13673,55 +13744,9 @@ paths:
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 更新套餐系列分配
|
||||
summary: 更新系列分配
|
||||
tags:
|
||||
- 套餐系列分配
|
||||
/api/admin/shop-series-allocations/{id}/status:
|
||||
put:
|
||||
parameters:
|
||||
- description: ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
description: ID
|
||||
minimum: 0
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DtoUpdateStatusParams'
|
||||
responses:
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 请求参数错误
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 未认证或认证已过期
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 无权访问
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
description: 服务器内部错误
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 更新套餐系列分配状态
|
||||
tags:
|
||||
- 套餐系列分配
|
||||
- 套餐分配
|
||||
/api/admin/shops:
|
||||
get:
|
||||
parameters:
|
||||
|
||||
351
docs/commission-package-model.md
Normal file
351
docs/commission-package-model.md
Normal file
@@ -0,0 +1,351 @@
|
||||
# 套餐与佣金业务模型
|
||||
|
||||
本文档定义了套餐、套餐系列、佣金的完整业务模型,作为系统改造的规范参考。
|
||||
|
||||
---
|
||||
|
||||
## 一、核心概念
|
||||
|
||||
### 1.1 两种佣金类型
|
||||
|
||||
系统只有两种佣金类型:
|
||||
|
||||
| 佣金类型 | 触发时机 | 触发次数 | 计算方式 |
|
||||
|---------|---------|---------|---------|
|
||||
| **差价佣金** | 每笔订单 | 每单都触发 | 下级成本价 - 自己成本价 |
|
||||
| **一次性佣金** | 首充/累计充值达标 | 每张卡/设备只触发一次 | 上级给的 - 给下级的 |
|
||||
|
||||
### 1.2 实体关系
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ 套餐系列 │
|
||||
│ PackageSeries │
|
||||
├─────────────────┤
|
||||
│ • 系列名称 │
|
||||
│ • 一次性佣金规则 │ ← 可选配置
|
||||
└────────┬────────┘
|
||||
│ 1:N
|
||||
▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ 套餐 │ │ 卡/设备 │
|
||||
│ Package │ │ IoT/Device │
|
||||
├─────────────────┤ ├─────────────────┤
|
||||
│ • 成本价 │ │ • 绑定系列ID │
|
||||
│ • 建议售价 │ │ • 累计充值金额 │ ← 按系列累计
|
||||
│ • 真流量(必填) │ │ • 是否已首充 │ ← 按系列记录
|
||||
│ • 虚流量(可选) │ └────────┬────────┘
|
||||
│ • 虚流量开关 │ │
|
||||
└────────┬────────┘ │ 分配
|
||||
│ ▼
|
||||
│ 分配 ┌─────────────────┐
|
||||
▼ │ 店铺 │
|
||||
┌─────────────────┐ │ Shop │
|
||||
│ 套餐分配 │◀─────────┤ • 代理层级 │
|
||||
│ PkgAllocation │ │ • 上级店铺ID │
|
||||
├─────────────────┤ └─────────────────┘
|
||||
│ • 店铺ID │
|
||||
│ • 套餐ID │
|
||||
│ • 成本价(加价后)│
|
||||
│ • 一次性佣金额 │ ← 给该代理的金额
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、套餐模型
|
||||
|
||||
### 2.1 字段定义
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `cost_price` | int64 | 是 | 成本价(平台设置的基础成本价,分) |
|
||||
| `suggested_price` | int64 | 是 | 建议售价(给代理参考,分) |
|
||||
| `real_data_mb` | int64 | 是 | 真实流量额度(MB) |
|
||||
| `enable_virtual_data` | bool | 否 | 是否启用虚流量 |
|
||||
| `virtual_data_mb` | int64 | 否 | 虚流量额度(启用时必填,≤ 真实流量,MB) |
|
||||
|
||||
### 2.2 流量停机判断
|
||||
|
||||
```
|
||||
停机目标值 = enable_virtual_data ? virtual_data_mb : real_data_mb
|
||||
```
|
||||
|
||||
### 2.3 不同用户视角
|
||||
|
||||
| 用户类型 | 看到的成本价 | 看到的一次性佣金 |
|
||||
|---------|-------------|-----------------|
|
||||
| 平台 | 基础成本价 | 完整规则 |
|
||||
| 代理A | A的成本价(已加价) | A能拿到的金额 |
|
||||
| 代理A1 | A1的成本价(再加价) | A1能拿到的金额 |
|
||||
|
||||
---
|
||||
|
||||
## 三、差价佣金
|
||||
|
||||
### 3.1 计算规则
|
||||
|
||||
```
|
||||
平台设置基础成本价: 100
|
||||
│
|
||||
│ 分配给代理A,设置成本价: 120
|
||||
▼
|
||||
代理A成本价: 120
|
||||
│
|
||||
│ 分配给代理A1,设置成本价: 130
|
||||
▼
|
||||
代理A1成本价: 130
|
||||
│
|
||||
│ A1销售给客户,售价: 200
|
||||
▼
|
||||
|
||||
结果:
|
||||
• A1 收入 = 200 - 130 = 70元(销售利润,不是佣金)
|
||||
• A 佣金 = 130 - 120 = 10元(差价佣金)
|
||||
• 平台收入 = 120元
|
||||
```
|
||||
|
||||
### 3.2 关键区分
|
||||
|
||||
- **收入/利润**:末端代理的 `售价 - 自己成本价`
|
||||
- **差价佣金**:上级代理的 `下级成本价 - 自己成本价`
|
||||
- **平台收入**:一级代理的成本价
|
||||
|
||||
---
|
||||
|
||||
## 四、一次性佣金
|
||||
|
||||
### 4.1 触发条件
|
||||
|
||||
| 条件类型 | 说明 | 强充要求 |
|
||||
|---------|------|---------|
|
||||
| `first_recharge` | 首充:该卡/设备在该系列下的第一次充值 | 必须强充 |
|
||||
| `accumulated_recharge` | 累计充值:累计充值金额达到阈值 | 可选强充 |
|
||||
|
||||
### 4.2 规则配置(套餐系列层面)
|
||||
|
||||
| 配置项 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `enable` | bool | 是否启用一次性佣金 |
|
||||
| `trigger_type` | string | 触发类型:`first_recharge` / `accumulated_recharge` |
|
||||
| `threshold` | int64 | 触发阈值(分):首充要求金额 或 累计要求金额 |
|
||||
| `commission_type` | string | 返佣类型:`fixed`(固定) / `tiered`(梯度) |
|
||||
| `commission_amount` | int64 | 固定返佣金额(fixed类型时) |
|
||||
| `tiers` | array | 梯度配置(tiered类型时) |
|
||||
| `validity_type` | string | 时效类型:`permanent` / `fixed_date` / `relative` |
|
||||
| `validity_value` | string | 时效值(到期日期 或 月数) |
|
||||
| `enable_force_recharge` | bool | 是否启用强充 |
|
||||
| `force_calc_type` | string | 强充金额计算:`fixed`(固定) / `dynamic`(动态差额) |
|
||||
| `force_amount` | int64 | 强充金额(fixed类型时) |
|
||||
|
||||
### 4.3 链式分配
|
||||
|
||||
一次性佣金在整条代理链上按约定分配:
|
||||
|
||||
```
|
||||
系列规则:首充100返20
|
||||
|
||||
分配配置:
|
||||
平台给A:20元
|
||||
A给A1:8元
|
||||
A1给A2:5元
|
||||
|
||||
触发首充时:
|
||||
A2 获得:5元
|
||||
A1 获得:8 - 5 = 3元
|
||||
A 获得:20 - 8 = 12元
|
||||
─────────────────────
|
||||
合计:20元 ✓
|
||||
```
|
||||
|
||||
### 4.4 首充流程
|
||||
|
||||
```
|
||||
客户购买套餐
|
||||
│
|
||||
▼
|
||||
预检:系列是否启用一次性佣金且为首充?
|
||||
│
|
||||
否 ───────────────────▶ 正常购买流程
|
||||
│
|
||||
是
|
||||
│
|
||||
▼
|
||||
该卡/设备在该系列下是否已首充过?
|
||||
│
|
||||
是 ───────────────────▶ 正常购买流程(不再返佣)
|
||||
│
|
||||
否
|
||||
│
|
||||
▼
|
||||
计算强充金额 = max(首充要求, 套餐售价)
|
||||
│
|
||||
▼
|
||||
返回提示:"需要充值 xxx 元"
|
||||
│
|
||||
▼
|
||||
用户确认 → 创建充值订单(金额=强充金额)
|
||||
│
|
||||
▼
|
||||
用户支付
|
||||
│
|
||||
▼
|
||||
支付成功:
|
||||
1. 钱进入钱包
|
||||
2. 标记该卡/设备已首充
|
||||
3. 自动创建套餐购买订单并完成
|
||||
4. 扣款(套餐售价)
|
||||
5. 触发一次性佣金,链式分配
|
||||
```
|
||||
|
||||
### 4.5 累计充值流程
|
||||
|
||||
```
|
||||
客户充值(直接充值到钱包)
|
||||
│
|
||||
▼
|
||||
累计充值金额 += 本次充值金额
|
||||
│
|
||||
▼
|
||||
该卡/设备是否已触发过累计充值返佣?
|
||||
│
|
||||
是 ───────────────────▶ 结束(不再返佣)
|
||||
│
|
||||
否
|
||||
│
|
||||
▼
|
||||
累计金额 >= 累计要求?
|
||||
│
|
||||
否 ───────────────────▶ 结束(继续累计)
|
||||
│
|
||||
是
|
||||
│
|
||||
▼
|
||||
触发一次性佣金,链式分配
|
||||
标记该卡/设备已触发累计充值返佣
|
||||
```
|
||||
|
||||
**累计规则**:
|
||||
|
||||
| 操作类型 | 是否累计 |
|
||||
|---------|---------|
|
||||
| 直接充值到钱包 | ✅ 累计 |
|
||||
| 直接购买套餐(不经过钱包) | ❌ 不累计 |
|
||||
| 强充购买套餐(先充值再扣款) | ✅ 累计(充值部分) |
|
||||
|
||||
---
|
||||
|
||||
## 五、梯度佣金
|
||||
|
||||
梯度佣金是一次性佣金的进阶版,根据代理销量/销售额动态调整返佣金额。
|
||||
|
||||
### 5.1 配置项
|
||||
|
||||
| 配置项 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `tier_dimension` | string | 梯度维度:`sales_count`(销量) / `sales_amount`(销售额) |
|
||||
| `stat_scope` | string | 统计范围:`self`(仅自己) / `self_and_sub`(自己+下级) |
|
||||
| `tiers` | array | 梯度档位列表 |
|
||||
| `tiers[].threshold` | int64 | 阈值(销量或销售额) |
|
||||
| `tiers[].amount` | int64 | 返佣金额(分) |
|
||||
|
||||
### 5.2 示例
|
||||
|
||||
```
|
||||
梯度规则(销量维度):
|
||||
┌────────────────┬────────────────────────┐
|
||||
│ 销量区间 │ 首充100返佣金额 │
|
||||
├────────────────┼────────────────────────┤
|
||||
│ >= 0 │ 5元 │
|
||||
├────────────────┼────────────────────────┤
|
||||
│ >= 100 │ 10元 │
|
||||
├────────────────┼────────────────────────┤
|
||||
│ >= 200 │ 20元 │
|
||||
└────────────────┴────────────────────────┘
|
||||
|
||||
代理A当前销量150单 → 落在 [100, 200) 区间 → 首充返10元
|
||||
```
|
||||
|
||||
### 5.3 梯度升级
|
||||
|
||||
```
|
||||
初始状态:
|
||||
代理A 销量150(适用10元档),给A1设置5元
|
||||
|
||||
触发时:A1得5元,A得10-5=5元
|
||||
|
||||
升级后(A销量达到210):
|
||||
A 适用20元档,A1配置仍为5元
|
||||
|
||||
触发时:A1得5元(不变),A得20-5=15元(增量归上级)
|
||||
```
|
||||
|
||||
### 5.4 统计周期
|
||||
|
||||
- 统计周期与一次性佣金时效一致
|
||||
- 只统计该套餐系列下的销量/销售额
|
||||
|
||||
---
|
||||
|
||||
## 六、约束规则
|
||||
|
||||
### 6.1 套餐分配
|
||||
|
||||
1. 下级成本价 >= 自己成本价(不能亏本卖)
|
||||
2. 只能分配自己有权限的套餐给下级
|
||||
3. 只能分配给直属下级(不能跨级)
|
||||
|
||||
### 6.2 一次性佣金分配
|
||||
|
||||
4. 给下级的金额 <= 自己能拿到的金额
|
||||
5. 给下级的金额 >= 0(可以设为0,独吞全部)
|
||||
|
||||
### 6.3 流量
|
||||
|
||||
6. 虚流量 <= 真实流量
|
||||
|
||||
### 6.4 配置修改
|
||||
|
||||
7. 修改配置只影响之后的新订单
|
||||
8. 代理只能修改"给下级多少钱",不能修改触发规则
|
||||
9. 平台修改系列规则不影响已分配的代理,需收回重新分配
|
||||
|
||||
### 6.5 触发限制
|
||||
|
||||
10. 一次性佣金每张卡/设备只触发一次
|
||||
11. "首充"指该卡/设备在该系列下的第一次充值
|
||||
12. 累计充值只统计"充值"操作,不统计"直接购买"
|
||||
|
||||
---
|
||||
|
||||
## 七、操作流程
|
||||
|
||||
### 7.1 理想的线性流程
|
||||
|
||||
```
|
||||
1. 创建套餐系列
|
||||
└─▶ 可选:配置一次性佣金规则
|
||||
|
||||
2. 创建套餐
|
||||
└─▶ 归属到系列
|
||||
└─▶ 设置成本价、建议售价
|
||||
└─▶ 设置真流量(必填)、虚流量(可选)
|
||||
|
||||
3. 分配套餐给代理
|
||||
└─▶ 设置代理成本价(加价)
|
||||
└─▶ 如果系列启用一次性佣金:设置给代理的一次性佣金额度
|
||||
|
||||
4. 分配资产(卡/设备)给代理
|
||||
└─▶ 资产绑定的套餐系列自动跟着走
|
||||
|
||||
5. 代理销售
|
||||
└─▶ 客户购买套餐
|
||||
└─▶ 差价佣金自动计算并入账给上级
|
||||
└─▶ 满足一次性佣金条件时,按链式分配入账
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、与现有代码的差异
|
||||
|
||||
详见改造提案:[refactor-commission-package-model](../openspec/changes/refactor-commission-package-model/)
|
||||
395
docs/refactor-commission-package-model/前端接口迁移指南.md
Normal file
395
docs/refactor-commission-package-model/前端接口迁移指南.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# 套餐与佣金模型重构 - 前端接口迁移指南
|
||||
|
||||
> 版本: v1.1
|
||||
> 更新日期: 2026-02-03
|
||||
> 影响范围: 套餐管理、系列管理、分配管理相关接口
|
||||
|
||||
---
|
||||
|
||||
## 一、变更概述
|
||||
|
||||
本次重构主要目标:
|
||||
1. 简化套餐价格字段(移除语义不清的字段)
|
||||
2. 支持真流量/虚流量共存机制
|
||||
3. 实现一次性佣金链式分配(上级给下级设置金额)
|
||||
4. 统一分配模型
|
||||
|
||||
### ⚠️ 重要:废弃内容汇总
|
||||
|
||||
**请确保前端代码中不再使用以下内容:**
|
||||
|
||||
#### 已废弃的枚举值
|
||||
|
||||
| 旧值 | 新值 | 说明 |
|
||||
|------|------|------|
|
||||
| `single_recharge` | `first_recharge` | 触发类型:单次充值 → 首充 |
|
||||
|
||||
#### 已废弃的请求字段(系列分配接口)
|
||||
|
||||
以下字段在系列分配接口中**已完全移除**,前端不应再传递:
|
||||
|
||||
```json
|
||||
// ❌ 以下字段已废弃,请勿使用
|
||||
{
|
||||
"enable_one_time_commission": true, // 已废弃
|
||||
"one_time_commission_type": "fixed", // 已废弃
|
||||
"one_time_commission_trigger": "...", // 已废弃
|
||||
"one_time_commission_threshold": 10000, // 已废弃
|
||||
"one_time_commission_mode": "fixed", // 已废弃
|
||||
"one_time_commission_value": 5000, // 已废弃
|
||||
"enable_force_recharge": false, // 已废弃
|
||||
"force_recharge_amount": 0 // 已废弃
|
||||
}
|
||||
```
|
||||
|
||||
**替代方案**:一次性佣金规则现在在**套餐系列**中配置,系列分配只需设置 `one_time_commission_amount`。
|
||||
|
||||
#### 已废弃的响应字段
|
||||
|
||||
系列分配响应中不再返回以下字段:
|
||||
- `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`
|
||||
- `one_time_commission_tiers`(完整梯度配置)
|
||||
|
||||
---
|
||||
|
||||
## 二、套餐接口变更
|
||||
|
||||
### 2.1 创建套餐 `POST /api/admin/packages`
|
||||
|
||||
**❌ 移除字段**:
|
||||
```json
|
||||
{
|
||||
"price": 9900, // 已移除
|
||||
"data_type": "real", // 已移除
|
||||
"data_amount_mb": 1024 // 已移除
|
||||
}
|
||||
```
|
||||
|
||||
**✅ 新增字段**:
|
||||
```json
|
||||
{
|
||||
"enable_virtual_data": true, // 是否启用虚流量
|
||||
"real_data_mb": 1024, // 真流量额度(MB) - 必填
|
||||
"virtual_data_mb": 512, // 虚流量额度(MB) - 启用虚流量时必填
|
||||
"cost_price": 5000 // 成本价(分) - 必填
|
||||
}
|
||||
```
|
||||
|
||||
**完整请求示例**:
|
||||
```json
|
||||
{
|
||||
"package_code": "PKG_001",
|
||||
"package_name": "月度套餐",
|
||||
"series_id": 1,
|
||||
"package_type": "formal",
|
||||
"duration_months": 1,
|
||||
"real_data_mb": 1024,
|
||||
"virtual_data_mb": 512,
|
||||
"enable_virtual_data": true,
|
||||
"cost_price": 5000,
|
||||
"suggested_retail_price": 9900
|
||||
}
|
||||
```
|
||||
|
||||
**校验规则**:
|
||||
- 启用虚流量时 (`enable_virtual_data: true`):
|
||||
- `virtual_data_mb` 必须 > 0
|
||||
- `virtual_data_mb` 必须 ≤ `real_data_mb`
|
||||
|
||||
---
|
||||
|
||||
### 2.2 更新套餐 `PUT /api/admin/packages/:id`
|
||||
|
||||
字段变更同上,所有字段均为可选。
|
||||
|
||||
---
|
||||
|
||||
### 2.3 套餐列表/详情响应变更
|
||||
|
||||
**✅ 新增字段**(代理用户可见):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"package_code": "PKG_001",
|
||||
"package_name": "月度套餐",
|
||||
"real_data_mb": 1024,
|
||||
"virtual_data_mb": 512,
|
||||
"enable_virtual_data": true,
|
||||
"cost_price": 5000,
|
||||
"suggested_retail_price": 9900,
|
||||
|
||||
// 以下字段仅代理用户可见
|
||||
"one_time_commission_amount": 1000, // 该代理能拿到的一次性佣金(分)
|
||||
"profit_margin": 4900, // 利润空间(分)
|
||||
"current_commission_rate": "5.00元/单",
|
||||
"tier_info": {
|
||||
"current_rate": "5.00元/单",
|
||||
"next_threshold": 100,
|
||||
"next_rate": "8.00元/单"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `cost_price`: 对于平台/平台用户是基础成本价,对于代理用户是该代理的成本价(从分配关系中获取)
|
||||
- `one_time_commission_amount`: 该代理能拿到的一次性佣金金额
|
||||
|
||||
---
|
||||
|
||||
## 三、套餐系列接口变更
|
||||
|
||||
### 3.1 创建/更新套餐系列
|
||||
|
||||
**✅ 新增嵌套结构 `one_time_commission_config`**:
|
||||
|
||||
```json
|
||||
{
|
||||
"series_code": "SERIES_001",
|
||||
"series_name": "标准套餐系列",
|
||||
"description": "包含所有标准流量套餐",
|
||||
"one_time_commission_config": {
|
||||
"enable": true,
|
||||
"trigger_type": "first_recharge",
|
||||
"threshold": 10000,
|
||||
"commission_type": "fixed",
|
||||
"commission_amount": 5000,
|
||||
"validity_type": "permanent",
|
||||
"validity_value": "",
|
||||
"enable_force_recharge": false,
|
||||
"force_calc_type": "fixed",
|
||||
"force_amount": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `enable` | boolean | 是否启用一次性佣金 |
|
||||
| `trigger_type` | string | 触发类型: `first_recharge`(首充) / `accumulated_recharge`(累计充值) |
|
||||
| `threshold` | int64 | 触发阈值(分) |
|
||||
| `commission_type` | string | 佣金类型: `fixed`(固定) / `tiered`(梯度) |
|
||||
| `commission_amount` | int64 | 固定佣金金额(分),`commission_type=fixed` 时使用 |
|
||||
| `validity_type` | string | 时效类型: `permanent`(永久) / `fixed_date`(固定日期) / `relative`(相对时长) |
|
||||
| `validity_value` | string | 时效值: 日期(2026-12-31) 或 月数(12) |
|
||||
| `enable_force_recharge` | boolean | 是否启用强充 |
|
||||
| `force_calc_type` | string | 强充计算类型: `fixed`(固定) / `dynamic`(动态) |
|
||||
| `force_amount` | int64 | 强充金额(分),`force_calc_type=fixed` 时使用 |
|
||||
|
||||
---
|
||||
|
||||
## 四、系列分配接口变更
|
||||
|
||||
### 4.1 创建系列分配 `POST /api/admin/shop-series-allocations`
|
||||
|
||||
**❌ 移除字段**(旧接口中的一次性佣金完整配置):
|
||||
```json
|
||||
{
|
||||
"enable_one_time_commission": true,
|
||||
"one_time_commission_type": "fixed",
|
||||
"one_time_commission_trigger": "single_recharge",
|
||||
"one_time_commission_threshold": 10000,
|
||||
"one_time_commission_mode": "fixed",
|
||||
"one_time_commission_value": 5000,
|
||||
"enable_force_recharge": false,
|
||||
"force_recharge_amount": 0
|
||||
}
|
||||
```
|
||||
|
||||
**✅ 新增字段**:
|
||||
```json
|
||||
{
|
||||
"shop_id": 10,
|
||||
"series_id": 1,
|
||||
"base_commission": {
|
||||
"mode": "fixed",
|
||||
"value": 500
|
||||
},
|
||||
"one_time_commission_amount": 5000 // 给被分配店铺的一次性佣金金额(分)
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 一次性佣金的规则(触发条件、阈值、时效等)现在在**套餐系列**中统一配置
|
||||
- 系列分配只需要设置**给下级的金额**
|
||||
|
||||
---
|
||||
|
||||
### 4.2 系列分配响应
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"shop_id": 10,
|
||||
"shop_name": "测试店铺",
|
||||
"series_id": 1,
|
||||
"series_name": "标准套餐系列",
|
||||
"allocator_shop_id": 5,
|
||||
"allocator_shop_name": "上级店铺",
|
||||
"base_commission": {
|
||||
"mode": "fixed",
|
||||
"value": 500
|
||||
},
|
||||
"one_time_commission_amount": 5000,
|
||||
"status": 1,
|
||||
"created_at": "2026-02-03T10:00:00Z",
|
||||
"updated_at": "2026-02-03T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、套餐分配接口变更
|
||||
|
||||
### 5.1 创建/更新套餐分配
|
||||
|
||||
**✅ 新增字段**:
|
||||
```json
|
||||
{
|
||||
"shop_id": 10,
|
||||
"package_id": 1,
|
||||
"cost_price": 6000,
|
||||
"one_time_commission_amount": 3000 // 给下级的一次性佣金金额(分)
|
||||
}
|
||||
```
|
||||
|
||||
**校验规则**:
|
||||
- `one_time_commission_amount` 必须 ≥ 0
|
||||
- `one_time_commission_amount` 不能超过上级能拿到的金额
|
||||
- 平台用户不受金额限制
|
||||
|
||||
---
|
||||
|
||||
### 5.2 套餐分配响应
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"shop_id": 10,
|
||||
"shop_name": "下级店铺",
|
||||
"package_id": 1,
|
||||
"package_name": "月度套餐",
|
||||
"package_code": "PKG_001",
|
||||
"allocation_id": 5,
|
||||
"cost_price": 6000,
|
||||
"calculated_cost_price": 5500,
|
||||
"one_time_commission_amount": 3000,
|
||||
"status": 1,
|
||||
"created_at": "2026-02-03T10:00:00Z",
|
||||
"updated_at": "2026-02-03T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、一次性佣金链式分配说明
|
||||
|
||||
### 6.1 概念
|
||||
|
||||
```
|
||||
平台设置系列一次性佣金规则:首充 100 元返 50 元
|
||||
|
||||
平台 → 一级代理 A(给 A 设置 40 元)
|
||||
↓
|
||||
一级代理 A → 二级代理 B(给 B 设置 25 元)
|
||||
↓
|
||||
二级代理 B → 三级代理 C(给 C 设置 10 元)
|
||||
```
|
||||
|
||||
当三级代理 C 的客户首充 100 元时:
|
||||
- 三级代理 C 获得: 10 元
|
||||
- 二级代理 B 获得: 25 - 10 = 15 元
|
||||
- 一级代理 A 获得: 40 - 25 = 15 元
|
||||
- 平台获得: 50 - 40 = 10 元
|
||||
|
||||
### 6.2 前端展示建议
|
||||
|
||||
在分配界面展示:
|
||||
- "上级能拿到的一次性佣金: 40 元"
|
||||
- "给下级设置的一次性佣金: [输入框,最大 40 元]"
|
||||
- "自己实际获得: [自动计算] 元"
|
||||
|
||||
---
|
||||
|
||||
## 七、枚举值参考
|
||||
|
||||
### 触发类型 (trigger_type)
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `first_recharge` | 首充触发 |
|
||||
| `accumulated_recharge` | 累计充值触发 |
|
||||
|
||||
### 佣金类型 (commission_type)
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `fixed` | 固定金额 |
|
||||
| `tiered` | 梯度(根据销量/销售额) |
|
||||
|
||||
### 时效类型 (validity_type)
|
||||
| 值 | 说明 | validity_value 格式 |
|
||||
|----|------|---------------------|
|
||||
| `permanent` | 永久有效 | 空 |
|
||||
| `fixed_date` | 固定到期日 | `2026-12-31` |
|
||||
| `relative` | 相对时长(激活后N月) | `12` |
|
||||
|
||||
### 强充计算类型 (force_calc_type)
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `fixed` | 固定金额 |
|
||||
| `dynamic` | 动态计算(max(首充要求, 套餐售价)) |
|
||||
|
||||
---
|
||||
|
||||
## 八、迁移检查清单
|
||||
|
||||
### 🔴 必须删除的代码
|
||||
|
||||
**请搜索并删除以下内容:**
|
||||
|
||||
```bash
|
||||
# 搜索废弃的枚举值
|
||||
grep -r "single_recharge" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.vue"
|
||||
|
||||
# 搜索废弃的字段名
|
||||
grep -r "one_time_commission_type\|one_time_commission_trigger\|one_time_commission_threshold\|one_time_commission_mode\|one_time_commission_value\|enable_one_time_commission\|force_recharge_amount\|enable_force_recharge" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.vue"
|
||||
```
|
||||
|
||||
### 套餐管理页面
|
||||
- [ ] 移除 `price`、`data_type`、`data_amount_mb` 字段
|
||||
- [ ] 新增 `enable_virtual_data` 开关
|
||||
- [ ] 新增虚流量校验逻辑(≤ 真流量)
|
||||
- [ ] 代理视角显示 `one_time_commission_amount`
|
||||
|
||||
### 套餐系列管理页面
|
||||
- [ ] 新增一次性佣金规则配置表单
|
||||
- [ ] 支持时效类型选择和值输入
|
||||
- [ ] 触发类型使用 `first_recharge`(不是旧的 `single_recharge`)
|
||||
|
||||
### 系列分配页面
|
||||
- [ ] **删除**旧的一次性佣金完整配置表单(8个字段)
|
||||
- [ ] **删除**梯度配置表单
|
||||
- [ ] 新增 `one_time_commission_amount` 输入(金额字段)
|
||||
- [ ] 显示上级能拿到的最大金额作为输入上限
|
||||
|
||||
### 套餐分配页面
|
||||
- [ ] 新增 `one_time_commission_amount` 输入
|
||||
- [ ] 显示校验错误(超过上级金额限制)
|
||||
|
||||
### 全局检查
|
||||
- [ ] 将所有 `single_recharge` 替换为 `first_recharge`
|
||||
- [ ] 移除系列分配相关的废弃字段引用
|
||||
- [ ] 更新 TypeScript 类型定义
|
||||
|
||||
---
|
||||
|
||||
## 九、联系方式
|
||||
|
||||
如有疑问,请联系后端开发团队。
|
||||
386
docs/workflow-optimization/方案总览.md
Normal file
386
docs/workflow-optimization/方案总览.md
Normal file
@@ -0,0 +1,386 @@
|
||||
# 工作流优化方案
|
||||
|
||||
## 一、背景与问题
|
||||
|
||||
### 1.1 当前痛点
|
||||
|
||||
| 痛点 | 根因 | 影响 |
|
||||
|------|------|------|
|
||||
| 讨论 → 提案不一致 | 共识没有被"锁定" | AI 理解偏差,提案与讨论方案不同 |
|
||||
| 提案 → 实现不一致 | 约束没有被"强制执行" | 实现细节偏离设计 |
|
||||
| 后置测试浪费时间 | 测试从实现反推 | 测试乱写、调试时间长 |
|
||||
| 单测意义不大 | 测试实现细节而非行为 | 重构就挂,维护成本高 |
|
||||
| 频繁重构 | 问题发现太晚 | 大量返工(17次/100提交) |
|
||||
|
||||
### 1.2 数据支撑
|
||||
|
||||
- **重构提交**: 17 次(近期约 100 次提交中)
|
||||
- **典型完成率**: 75%(Shop Package Allocation: 91/121 tasks)
|
||||
- **未完成原因**: 测试("低优先级,需要运行环境")
|
||||
- **TODO 残留**: 10+ 个(代码中待完成的功能)
|
||||
|
||||
---
|
||||
|
||||
## 二、解决方案概览
|
||||
|
||||
### 2.1 核心理念变化
|
||||
|
||||
```
|
||||
旧工作流:
|
||||
discuss → proposal → design → tasks → implement → test → verify
|
||||
↑ 测试后置
|
||||
问题发现太晚
|
||||
|
||||
新工作流:
|
||||
discuss → 锁定共识 → proposal → 验证 → design → 验证 →
|
||||
生成验收测试 → 实现(测试驱动)→ 验证 → 归档
|
||||
↑ ↑
|
||||
测试从 spec 生成 实现时对照测试
|
||||
```
|
||||
|
||||
### 2.2 新增机制
|
||||
|
||||
| 机制 | 解决的问题 | 实现方式 |
|
||||
|------|-----------|---------|
|
||||
| **共识锁定** | 讨论→提案不一致 | `consensus.md` + 用户确认 |
|
||||
| **验收测试先行** | 测试后置浪费时间 | 从 Spec 生成测试,实现前运行 |
|
||||
| **业务流程测试** | 跨 API 场景验证 | 从 Business Flow 生成测试 |
|
||||
| **中间验证** | 问题发现太晚 | 每个 artifact 后自动验证 |
|
||||
| **约束检查** | 实现偏离设计 | 实现时对照约束清单 |
|
||||
|
||||
---
|
||||
|
||||
## 三、新工作流详解
|
||||
|
||||
### 3.1 完整流程图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 1: 探索 & 锁定共识 │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐ │
|
||||
│ │ /opsx:explore │ ──▶ │ 讨论并确认共识 │ ──▶ │ consensus.md │ │
|
||||
│ └──────────────┘ │ AI 输出共识摘要 │ │ 用户确认后锁定 │ │
|
||||
│ │ 用户逐条确认 ✓ │ └─────────────────┘ │
|
||||
│ └──────────────────────┘ │
|
||||
│ │
|
||||
│ 输出: openspec/changes/<name>/consensus.md (用户签字确认版) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 2: 生成提案 & 验证 │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 读取 consensus │ ──▶ │ 生成 proposal.md │ ──▶ │ 自动验证 │ │
|
||||
│ └──────────────┘ │ 必须覆盖共识要点 │ │ proposal 与 │ │
|
||||
│ └──────────────────────┘ │ consensus 对齐 │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
│ 验证: 共识中的每个"要做什么"都在 proposal 的 Capabilities 中出现 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 3: 生成 Spec │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ │
|
||||
│ │ 生成 spec.md │ ──▶ │ 包含两部分: │ │
|
||||
│ │ │ │ 1. Scenarios │ │
|
||||
│ │ │ │ 2. Business Flows │ │
|
||||
│ └──────────────┘ └──────────────────────┘ │
|
||||
│ │
|
||||
│ Scenario: 单 API 的输入输出契约 │
|
||||
│ Business Flow: 多 API 组合的业务场景 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 4: 生成测试(关键变化!) │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐ │
|
||||
│ │ /opsx:gen-tests │ ──▶ │ 生成两类测试: │ ──▶ │ 运行测试 │ │
|
||||
│ └──────────────┘ │ 1. 验收测试 │ │ 预期全部 FAIL │ │
|
||||
│ │ 2. 流程测试 │ │ ← 证明测试有效 │ │
|
||||
│ └──────────────────────┘ └─────────────────┘ │
|
||||
│ │
|
||||
│ 输出: │
|
||||
│ - tests/acceptance/{capability}_acceptance_test.go │
|
||||
│ - tests/flows/{capability}_{flow}_flow_test.go │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 5: 设计 & 实现(测试驱动) │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 生成 design │ ──▶ │ 生成 tasks.md │ ──▶ │ 实现每个 task │ │
|
||||
│ │ + 约束清单 │ │ 每个 task 关联测试 │ │ 运行对应测试 │ │
|
||||
│ └──────────────┘ └──────────────────────┘ │ 测试通过才继续 │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
│ 实现循环: │
|
||||
│ for each task: │
|
||||
│ 1. 运行关联的测试 (预期 FAIL) │
|
||||
│ 2. 实现代码 │
|
||||
│ 3. 运行测试 (预期 PASS) │
|
||||
│ 4. 测试通过 → 标记 task 完成 │
|
||||
│ 5. 测试失败 → 修复代码,重复步骤 3 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 6: 最终验证 & 归档 │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 运行全部测试 │ ──▶ │ 生成完成报告 │ ──▶ │ 归档 change │ │
|
||||
│ │ 必须 100% PASS │ │ 包含测试覆盖证据 │ └─────────────────┘ │
|
||||
│ └──────────────┘ └──────────────────────┘ │
|
||||
│ │
|
||||
│ 完成报告必须包含: │
|
||||
│ - 验收测试通过截图/日志 │
|
||||
│ - 流程测试通过截图/日志 │
|
||||
│ - 每个 Scenario/Flow 的测试对应关系 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 命令对照表
|
||||
|
||||
| 步骤 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| 1 | `/opsx:explore` | 探索讨论 |
|
||||
| 2 | `/opsx:lock <name>` | **新** 锁定共识 |
|
||||
| 3 | `/opsx:new <name>` | 创建 change(自动读取 consensus) |
|
||||
| 4 | `/opsx:continue` | 生成 proposal |
|
||||
| 5 | `/opsx:continue` | 生成 spec |
|
||||
| 6 | `/opsx:gen-tests` | **新** 生成验收测试和流程测试 |
|
||||
| 7 | `/opsx:continue` | 生成 design |
|
||||
| 8 | `/opsx:continue` | 生成 tasks |
|
||||
| 9 | `/opsx:apply` | 测试驱动实现 |
|
||||
| 10 | `/opsx:verify` | 验证 |
|
||||
| 11 | `/opsx:archive` | 归档 |
|
||||
|
||||
---
|
||||
|
||||
## 四、测试体系重设计
|
||||
|
||||
### 4.1 新测试金字塔
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ E2E 测试 │ ← 手动/自动化 UI(很少)
|
||||
│ │
|
||||
─┴─────────────┴─
|
||||
┌─────────────────┐
|
||||
│ 业务流程测试 │ ← 新增!多 API 组合
|
||||
│ tests/flows/ │ 验证业务场景完整性
|
||||
─┴─────────────────┴─
|
||||
┌─────────────────────┐
|
||||
│ 验收测试 │ ← 新增!从 Spec Scenario 生成
|
||||
│ tests/acceptance/ │ 单 API 契约验证
|
||||
─┴─────────────────────┴─
|
||||
┌───────────────────────────┐
|
||||
│ 集成冒烟测试 │ ← 保留
|
||||
│ tests/integration/ │
|
||||
─┴───────────────────────────┴─
|
||||
┌─────────────────────────────────┐
|
||||
│ 单元测试 (精简!) │ ← 大幅减少
|
||||
│ tests/unit/ │ 仅复杂逻辑
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 三层测试体系
|
||||
|
||||
| 层级 | 测试类型 | 来源 | 验证什么 | 位置 |
|
||||
|------|---------|------|---------|------|
|
||||
| **L1** | 验收测试 | Spec Scenario | 单 API 契约 | `tests/acceptance/` |
|
||||
| **L2** | 流程测试 | Spec Business Flow | 业务场景完整性 | `tests/flows/` |
|
||||
| **L3** | 单元测试 | 复杂逻辑 | 算法/规则正确性 | `tests/unit/` |
|
||||
|
||||
### 4.3 测试比例调整
|
||||
|
||||
| 测试类型 | 旧占比 | 新占比 | 变化 |
|
||||
|---------|-------|-------|------|
|
||||
| 验收测试 | 0% | **30%** | 新增 |
|
||||
| 流程测试 | 0% | **15%** | 新增 |
|
||||
| 集成测试 | 28% | 25% | 略减 |
|
||||
| 单元测试 | 72% | **30%** | 大幅减少 |
|
||||
|
||||
### 4.4 单元测试精简规则
|
||||
|
||||
**保留**:
|
||||
- ✅ 纯函数(计费计算、分佣算法)
|
||||
- ✅ 状态机(订单状态流转)
|
||||
- ✅ 复杂业务规则(层级校验、权限计算)
|
||||
- ✅ 边界条件(时间、金额、精度)
|
||||
|
||||
**删除/不再写**:
|
||||
- ❌ 简单 CRUD(已被验收测试覆盖)
|
||||
- ❌ DTO 转换
|
||||
- ❌ 配置读取
|
||||
- ❌ 重复测试同一逻辑
|
||||
|
||||
---
|
||||
|
||||
## 五、Spec 模板更新
|
||||
|
||||
### 5.1 新 Spec 结构
|
||||
|
||||
```markdown
|
||||
# {capability} Specification
|
||||
|
||||
## Purpose
|
||||
{简要描述这个能力的目的}
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: {requirement-name}
|
||||
{详细描述}
|
||||
|
||||
#### Scenario: {scenario-name}
|
||||
- **GIVEN** {前置条件}
|
||||
- **WHEN** {触发动作}
|
||||
- **THEN** {预期结果}
|
||||
- **AND** {额外验证}
|
||||
|
||||
---
|
||||
|
||||
## Business Flows(新增必填部分)
|
||||
|
||||
### Flow: {flow-name}
|
||||
|
||||
**参与者**: {角色1}, {角色2}, ...
|
||||
|
||||
**前置条件**:
|
||||
- {条件1}
|
||||
- {条件2}
|
||||
|
||||
**流程步骤**:
|
||||
|
||||
1. **{步骤名称}**
|
||||
- 角色: {执行角色}
|
||||
- 调用: {HTTP Method} {Path}
|
||||
- 输入: {关键参数}
|
||||
- 预期: {预期结果}
|
||||
- 验证: {数据库/缓存状态变化}
|
||||
|
||||
2. **{下一步骤}**
|
||||
...
|
||||
|
||||
**流程图**:
|
||||
```
|
||||
[角色A] ──创建──▶ [资源] ──分配──▶ [角色B可见] ──使用──▶ [状态变更]
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- [ ] {验证点1}
|
||||
- [ ] {验证点2}
|
||||
- [ ] 数据一致性: {描述}
|
||||
|
||||
**异常流程**:
|
||||
- 如果 {条件}: 预期 {结果}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、文件结构
|
||||
|
||||
### 6.1 测试目录
|
||||
|
||||
```
|
||||
tests/
|
||||
├── acceptance/ # 验收测试(单 API)
|
||||
│ ├── account_acceptance_test.go
|
||||
│ ├── package_acceptance_test.go
|
||||
│ ├── iot_card_acceptance_test.go
|
||||
│ └── README.md
|
||||
├── flows/ # 业务流程测试(多 API)
|
||||
│ ├── package_lifecycle_flow_test.go
|
||||
│ ├── order_purchase_flow_test.go
|
||||
│ ├── commission_settlement_flow_test.go
|
||||
│ └── README.md
|
||||
├── integration/ # 集成测试(保留)
|
||||
│ └── ...
|
||||
├── unit/ # 单元测试(精简)
|
||||
│ └── ...
|
||||
└── testutils/
|
||||
└── integ/
|
||||
└── integration.go
|
||||
```
|
||||
|
||||
### 6.2 OpenSpec 目录
|
||||
|
||||
```
|
||||
openspec/
|
||||
├── config.yaml # 更新:增加测试规则
|
||||
├── changes/
|
||||
│ └── <change-name>/
|
||||
│ ├── consensus.md # 新增:共识确认单
|
||||
│ ├── proposal.md
|
||||
│ ├── design.md
|
||||
│ ├── tasks.md
|
||||
│ └── specs/
|
||||
│ └── <capability>/
|
||||
│ └── spec.md # 更新:包含 Business Flows
|
||||
└── specs/
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、实施计划
|
||||
|
||||
### Phase 1: 基础设施(2-3 天)
|
||||
|
||||
1. 创建目录结构
|
||||
2. 更新 `openspec/config.yaml`
|
||||
3. 创建 `openspec-lock-consensus` skill
|
||||
4. 创建 `openspec-generate-acceptance-tests` skill
|
||||
5. 更新 `AGENTS.md` 测试规范
|
||||
6. 创建 `tests/acceptance/README.md`
|
||||
7. 创建 `tests/flows/README.md`
|
||||
|
||||
### Phase 2: 试点(1 周)
|
||||
|
||||
选择一个新 feature 完整走一遍新流程:
|
||||
1. 验证共识锁定机制
|
||||
2. 验证测试生成
|
||||
3. 验证测试驱动实现
|
||||
4. 收集反馈,调整流程
|
||||
|
||||
### Phase 3: 推广(持续)
|
||||
|
||||
1. 新 feature 强制使用新流程
|
||||
2. 现有高价值测试迁移为验收测试
|
||||
3. 清理低价值单元测试
|
||||
4. 建立测试覆盖率追踪
|
||||
|
||||
---
|
||||
|
||||
## 八、预期收益
|
||||
|
||||
| 指标 | 当前 | 预期 |
|
||||
|------|------|------|
|
||||
| 讨论→提案一致率 | ~60% | >95% |
|
||||
| 提案→实现一致率 | ~70% | >95% |
|
||||
| 测试编写时间 | 实现后补,耗时长 | 实现前生成,自动化 |
|
||||
| 测试有效性 | 很多无效测试 | 每个测试有破坏点 |
|
||||
| 重构频率 | 高(17次/100提交) | 低(问题早发现) |
|
||||
| 单测维护成本 | 高(重构就挂) | 低(只测行为) |
|
||||
| 业务流程正确性 | 无保证 | 流程测试覆盖 |
|
||||
|
||||
---
|
||||
|
||||
## 九、相关文档
|
||||
|
||||
- [验收测试说明](../../tests/acceptance/README.md)
|
||||
- [流程测试说明](../../tests/flows/README.md)
|
||||
- [共识锁定 Skill](.opencode/skills/openspec-lock-consensus/SKILL.md)
|
||||
- [测试生成 Skill](.opencode/skills/openspec-generate-acceptance-tests/SKILL.md)
|
||||
- [AGENTS.md 测试规范](../../AGENTS.md#测试要求)
|
||||
Reference in New Issue
Block a user