重构: 店铺套餐分配系统从加价模式改为返佣模式
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m18s

主要变更:
- 重构分配模型:从加价模式(pricing_mode/pricing_value)改为返佣模式(base_commission + tier_commission)
- 删除独立的 my_package 接口,统一到 /api/admin/packages(通过数据权限自动过滤)
- 新增批量分配和批量调价功能,支持事务和性能优化
- 新增配置版本管理,订单创建时锁定返佣配置
- 新增成本价历史记录,支持审计和纠纷处理
- 新增统计缓存系统(Redis + 异步任务),优化梯度返佣计算性能
- 删除冗余的梯度佣金独立 CRUD 接口(合并到分配配置中)
- 归档 3 个已完成的 OpenSpec changes 并同步 8 个新 capabilities 到 main specs

技术细节:
- 数据库迁移:000026_refactor_shop_package_allocation
- 新增 Store:AllocationConfigStore, PriceHistoryStore, CommissionStatsStore
- 新增 Service:BatchAllocationService, BatchPricingService, CommissionStatsService
- 新增异步任务:统计更新、定时同步、周期归档
- 测试覆盖:批量操作集成测试、梯度佣金 CRUD 清理验证

影响:
- API 变更:删除 4 个梯度 CRUD 接口(POST/GET/PUT/DELETE /:id/tiers)
- API 新增:批量分配、批量调价接口
- 数据模型:重构 shop_series_allocation 表结构
- 性能优化:批量操作使用 CreateInBatches,统计使用 Redis 缓存

相关文档:
- openspec/changes/archive/2026-01-28-refactor-shop-package-allocation/
- openspec/specs/agent-available-packages/
- openspec/specs/allocation-config-versioning/
- 等 8 个新 capability specs
This commit is contained in:
2026-01-28 17:11:55 +08:00
parent 23eb0307bb
commit 1da680a790
97 changed files with 6810 additions and 3622 deletions

View File

@@ -525,6 +525,19 @@ components:
description: 总记录数
type: integer
type: object
DtoBaseCommissionConfig:
properties:
mode:
description: 返佣模式 (fixed:固定金额, percent:百分比)
type: string
value:
description: 返佣值分或千分比如200=20%
minimum: 0
type: integer
required:
- mode
- value
type: object
DtoBindCardToDeviceRequest:
properties:
iot_card_id:
@@ -606,49 +619,18 @@ components:
old_password:
type: string
type: object
DtoCommissionTierListResult:
DtoCommissionTierInfo:
properties:
list:
description: 梯度佣金列表
items:
$ref: '#/components/schemas/DtoCommissionTierResponse'
current_rate:
description: 当前返佣比例
type: string
next_rate:
description: 下一档位返佣比例
type: string
next_threshold:
description: 下一档位阈值
nullable: true
type: array
type: object
DtoCommissionTierResponse:
properties:
allocation_id:
description: 关联的分配ID
minimum: 0
type: integer
commission_amount:
description: 佣金金额(分)
type: integer
created_at:
description: 创建时间
type: string
id:
description: 梯度ID
minimum: 0
type: integer
period_end_date:
description: 自定义周期结束日期
type: string
period_start_date:
description: 自定义周期开始日期
type: string
period_type:
description: 周期类型 (monthly:月度, quarterly:季度, yearly:年度, custom:自定义)
type: string
threshold_value:
description: 阈值
type: integer
tier_type:
description: 梯度类型 (sales_count:销量, sales_amount:销售额)
type: string
updated_at:
description: 更新时间
type: string
type: object
DtoCreateAccountRequest:
properties:
@@ -712,36 +694,6 @@ components:
- carrier_name
- carrier_type
type: object
DtoCreateCommissionTierParams:
properties:
commission_amount:
description: 佣金金额(分)
minimum: 1
type: integer
period_end_date:
description: 自定义周期结束日期(YYYY-MM-DD)当周期类型为custom时必填
nullable: true
type: string
period_start_date:
description: 自定义周期开始日期(YYYY-MM-DD)当周期类型为custom时必填
nullable: true
type: string
period_type:
description: 周期类型 (monthly:月度, quarterly:季度, yearly:年度, custom:自定义)
type: string
threshold_value:
description: 阈值(销量或金额分)
minimum: 1
type: integer
tier_type:
description: 梯度类型 (sales_count:销量, sales_amount:销售额)
type: string
required:
- tier_type
- period_type
- threshold_value
- commission_amount
type: object
DtoCreateCustomerAccountReq:
properties:
password:
@@ -1150,24 +1102,11 @@ components:
type: object
DtoCreateShopSeriesAllocationRequest:
properties:
one_time_commission_amount:
description: 一次性佣金金额(分)
minimum: 0
type: integer
one_time_commission_threshold:
description: 一次性佣金触发阈值(分)
minimum: 0
type: integer
one_time_commission_trigger:
description: 一次性佣金触发类型 (one_time_recharge:单次充值, accumulated_recharge:累计充值)
type: string
pricing_mode:
description: 加价模式 (fixed:固定金额, percent:百分比)
type: string
pricing_value:
description: 加价值分或千分比如100=10%
minimum: 0
type: integer
base_commission:
$ref: '#/components/schemas/DtoBaseCommissionConfig'
enable_tier_commission:
description: 是否启用梯度返佣
type: boolean
series_id:
description: 套餐系列ID
minimum: 0
@@ -1176,11 +1115,12 @@ components:
description: 被分配的店铺ID
minimum: 0
type: integer
tier_config:
$ref: '#/components/schemas/DtoTierCommissionConfig'
required:
- shop_id
- series_id
- pricing_mode
- pricing_value
- base_commission
type: object
DtoCreateWithdrawalSettingReq:
properties:
@@ -2175,165 +2115,6 @@ components:
description: 已提现佣金(分)
type: integer
type: object
DtoMyPackageDetailResponse:
properties:
cost_price:
description: 我的成本价(分)
type: integer
description:
description: 套餐描述
type: string
id:
description: 套餐ID
minimum: 0
type: integer
package_code:
description: 套餐编码
type: string
package_name:
description: 套餐名称
type: string
package_type:
description: 套餐类型
type: string
price_source:
description: 价格来源 (series_pricing:系列加价, package_override:单套餐覆盖)
type: string
profit_margin:
description: 利润空间(分)
type: integer
series_id:
description: 套餐系列ID
minimum: 0
type: integer
series_name:
description: 套餐系列名称
type: string
shelf_status:
description: 上架状态 (1:上架, 2:下架)
type: integer
status:
description: 套餐状态 (1:启用, 2:禁用)
type: integer
suggested_retail_price:
description: 建议售价(分)
type: integer
type: object
DtoMyPackagePageResult:
properties:
list:
description: 套餐列表
items:
$ref: '#/components/schemas/DtoMyPackageResponse'
nullable: true
type: array
page:
description: 当前页
type: integer
page_size:
description: 每页数量
type: integer
total:
description: 总数
type: integer
total_pages:
description: 总页数
type: integer
type: object
DtoMyPackageResponse:
properties:
cost_price:
description: 我的成本价(分)
type: integer
id:
description: 套餐ID
minimum: 0
type: integer
package_code:
description: 套餐编码
type: string
package_name:
description: 套餐名称
type: string
package_type:
description: 套餐类型
type: string
price_source:
description: 价格来源 (series_pricing:系列加价, package_override:单套餐覆盖)
type: string
profit_margin:
description: 利润空间(分)= 建议售价 - 成本价
type: integer
series_id:
description: 套餐系列ID
minimum: 0
type: integer
series_name:
description: 套餐系列名称
type: string
shelf_status:
description: 上架状态 (1:上架, 2:下架)
type: integer
status:
description: 套餐状态 (1:启用, 2:禁用)
type: integer
suggested_retail_price:
description: 建议售价(分)
type: integer
type: object
DtoMySeriesAllocationPageResult:
properties:
list:
description: 分配列表
items:
$ref: '#/components/schemas/DtoMySeriesAllocationResponse'
nullable: true
type: array
page:
description: 当前页
type: integer
page_size:
description: 每页数量
type: integer
total:
description: 总数
type: integer
total_pages:
description: 总页数
type: integer
type: object
DtoMySeriesAllocationResponse:
properties:
allocator_shop_name:
description: 分配者店铺名称
type: string
available_package_count:
description: 可售套餐数量
type: integer
id:
description: 分配ID
minimum: 0
type: integer
pricing_mode:
description: 加价模式 (fixed:固定金额, percent:百分比)
type: string
pricing_value:
description: 加价值
type: integer
series_code:
description: 系列编码
type: string
series_id:
description: 套餐系列ID
minimum: 0
type: integer
series_name:
description: 系列名称
type: string
status:
description: 状态 (1:启用, 2:禁用)
type: integer
type: object
DtoPackagePageResult:
properties:
list:
@@ -2357,9 +2138,16 @@ components:
type: object
DtoPackageResponse:
properties:
cost_price:
description: 成本价(分,仅代理用户可见)
nullable: true
type: integer
created_at:
description: 创建时间
type: string
current_commission_rate:
description: 当前返佣比例(仅代理用户可见)
type: string
data_amount_mb:
description: 总流量额度(MB)
type: integer
@@ -2385,6 +2173,10 @@ components:
price:
description: 套餐价格(分)
type: integer
profit_margin:
description: 利润空间(分,仅代理用户可见)
nullable: true
type: integer
real_data_mb:
description: 真流量额度(MB)
type: integer
@@ -2393,6 +2185,10 @@ components:
minimum: 0
nullable: true
type: integer
series_name:
description: 套餐系列名称
nullable: true
type: string
shelf_status:
description: 上架状态 (1:上架, 2:下架)
type: integer
@@ -2405,6 +2201,8 @@ components:
suggested_retail_price:
description: 建议售价(分)
type: integer
tier_info:
$ref: '#/components/schemas/DtoCommissionTierInfo'
updated_at:
description: 更新时间
type: string
@@ -3107,31 +2905,18 @@ components:
allocator_shop_name:
description: 分配者店铺名称
type: string
calculated_cost_price:
description: 计算后的成本价(分)
type: integer
base_commission:
$ref: '#/components/schemas/DtoBaseCommissionConfig'
created_at:
description: 创建时间
type: string
enable_tier_commission:
description: 是否启用梯度返佣
type: boolean
id:
description: 分配ID
minimum: 0
type: integer
one_time_commission_amount:
description: 一次性佣金金额(分)
type: integer
one_time_commission_threshold:
description: 一次性佣金触发阈值(分)
type: integer
one_time_commission_trigger:
description: 一次性佣金触发类型
type: string
pricing_mode:
description: 加价模式 (fixed:固定金额, percent:百分比)
type: string
pricing_value:
description: 加价值(分或千分比)
type: integer
series_id:
description: 套餐系列ID
minimum: 0
@@ -3334,6 +3119,43 @@ components:
format: date-time
type: string
type: object
DtoTierCommissionConfig:
properties:
period_type:
description: 周期类型 (monthly:月度, quarterly:季度, yearly:年度)
type: string
tier_type:
description: 梯度类型 (sales_count:销量, sales_amount:销售额)
type: string
tiers:
description: 梯度档位列表
items:
$ref: '#/components/schemas/DtoTierEntry'
nullable: true
type: array
required:
- period_type
- tier_type
- tiers
type: object
DtoTierEntry:
properties:
mode:
description: 达标后返佣模式 (fixed:固定金额, percent:百分比)
type: string
threshold:
description: 阈值(销量或金额分)
minimum: 1
type: integer
value:
description: 达标后返佣值(分或千分比)
minimum: 1
type: integer
required:
- threshold
- mode
- value
type: object
DtoUnbindCardFromDeviceResponse:
properties:
message:
@@ -3395,35 +3217,6 @@ components:
required:
- status
type: object
DtoUpdateCommissionTierParams:
properties:
commission_amount:
description: 佣金金额(分)
minimum: 1
nullable: true
type: integer
period_end_date:
description: 自定义周期结束日期
nullable: true
type: string
period_start_date:
description: 自定义周期开始日期
nullable: true
type: string
period_type:
description: 周期类型
nullable: true
type: string
threshold_value:
description: 阈值
minimum: 1
nullable: true
type: integer
tier_type:
description: 梯度类型
nullable: true
type: string
type: object
DtoUpdateCustomerAccountPasswordReq:
properties:
password:
@@ -3790,29 +3583,14 @@ components:
type: object
DtoUpdateShopSeriesAllocationParams:
properties:
one_time_commission_amount:
description: 一次性佣金金额(分)
minimum: 0
base_commission:
$ref: '#/components/schemas/DtoBaseCommissionConfig'
enable_tier_commission:
description: 是否启用梯度返佣
nullable: true
type: integer
one_time_commission_threshold:
description: 一次性佣金触发阈值(分)
minimum: 0
nullable: true
type: integer
one_time_commission_trigger:
description: 一次性佣金触发类型
nullable: true
type: string
pricing_mode:
description: 加价模式 (fixed:固定金额, percent:百分比)
nullable: true
type: string
pricing_value:
description: 加价值(分或千分比)
minimum: 0
nullable: true
type: integer
type: boolean
tier_config:
$ref: '#/components/schemas/DtoTierCommissionConfig'
type: object
DtoUpdateStatusParams:
properties:
@@ -7624,176 +7402,6 @@ paths:
summary: 获取当前用户信息
tags:
- 认证
/api/admin/my-packages:
get:
parameters:
- description: 页码
in: query
name: page
schema:
description: 页码
minimum: 1
type: integer
- description: 每页数量
in: query
name: page_size
schema:
description: 每页数量
maximum: 100
minimum: 1
type: integer
- description: 套餐系列ID
in: query
name: series_id
schema:
description: 套餐系列ID
minimum: 0
nullable: true
type: integer
- description: 套餐类型
in: query
name: package_type
schema:
description: 套餐类型
nullable: true
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoMyPackagePageResult'
description: OK
"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/my-packages/{id}:
get:
parameters:
- description: ID
in: path
name: id
required: true
schema:
description: ID
minimum: 0
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoMyPackageDetailResponse'
description: OK
"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/my-series-allocations:
get:
parameters:
- description: 页码
in: query
name: page
schema:
description: 页码
minimum: 1
type: integer
- description: 每页数量
in: query
name: page_size
schema:
description: 每页数量
maximum: 100
minimum: 1
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoMySeriesAllocationPageResult'
description: OK
"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/my/commission-records:
get:
parameters:
@@ -10546,6 +10154,53 @@ paths:
summary: 更新单套餐分配
tags:
- 单套餐分配
/api/admin/shop-package-allocations/{id}/cost-price:
put:
parameters:
- description: ID
in: path
name: id
required: true
schema:
description: ID
minimum: 0
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoShopPackageAllocationResponse'
description: OK
"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/shop-package-allocations/{id}/status:
put:
parameters:
@@ -10895,212 +10550,6 @@ paths:
summary: 更新套餐系列分配状态
tags:
- 套餐系列分配
/api/admin/shop-series-allocations/{id}/tiers:
get:
parameters:
- description: ID
in: path
name: id
required: true
schema:
description: ID
minimum: 0
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoCommissionTierListResult'
description: OK
"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:
- 套餐系列分配
post:
parameters:
- description: ID
in: path
name: id
required: true
schema:
description: ID
minimum: 0
type: integer
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DtoCreateCommissionTierParams'
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoCommissionTierResponse'
description: OK
"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/shop-series-allocations/{id}/tiers/{tier_id}:
delete:
parameters:
- description: 分配ID
in: path
name: id
required: true
schema:
description: 分配ID
minimum: 0
type: integer
- description: 梯度ID
in: path
name: tier_id
required: true
schema:
description: 梯度ID
minimum: 0
type: integer
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:
- 套餐系列分配
put:
parameters:
- description: 分配ID
in: path
name: id
required: true
schema:
description: 分配ID
minimum: 0
type: integer
- description: 梯度ID
in: path
name: tier_id
required: true
schema:
description: 梯度ID
minimum: 0
type: integer
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DtoUpdateCommissionTierParams'
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoCommissionTierResponse'
description: OK
"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: