重构: 店铺套餐分配系统从加价模式改为返佣模式
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

@@ -0,0 +1,19 @@
package dto
// AllocationConfigResponse 配置版本响应
type AllocationConfigResponse struct {
ID uint `json:"id" description:"配置版本ID"`
AllocationID uint `json:"allocation_id" description:"关联的分配ID"`
Version int `json:"version" description:"配置版本号"`
BaseCommissionMode string `json:"base_commission_mode" description:"基础返佣模式 (fixed:固定金额, percent:百分比)"`
BaseCommissionValue int64 `json:"base_commission_value" description:"基础返佣值(分或千分比)"`
EnableTierCommission bool `json:"enable_tier_commission" description:"是否启用梯度返佣"`
EffectiveFrom string `json:"effective_from" description:"生效开始时间"`
EffectiveTo string `json:"effective_to,omitempty" description:"生效结束时间NULL表示当前生效"`
CreatedAt string `json:"created_at" description:"创建时间"`
}
// AllocationConfigListResponse 配置版本列表响应
type AllocationConfigListResponse struct {
List []*AllocationConfigResponse `json:"list" description:"配置版本列表"`
}

View File

@@ -0,0 +1,30 @@
package dto
// PriceHistoryResponse 成本价历史响应
type PriceHistoryResponse struct {
ID uint `json:"id" description:"历史记录ID"`
AllocationID uint `json:"allocation_id" description:"关联的套餐分配ID"`
OldCostPrice int64 `json:"old_cost_price" description:"原成本价(分)"`
NewCostPrice int64 `json:"new_cost_price" description:"新成本价(分)"`
ChangeReason string `json:"change_reason" description:"变更原因"`
ChangedBy uint `json:"changed_by" description:"变更人ID"`
ChangedByName string `json:"changed_by_name" description:"变更人姓名"`
EffectiveFrom string `json:"effective_from" description:"生效时间"`
CreatedAt string `json:"created_at" description:"创建时间"`
}
// PriceHistoryListRequest 成本价历史列表请求
type PriceHistoryListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
AllocationID *uint `json:"allocation_id" query:"allocation_id" validate:"omitempty" description:"套餐分配ID"`
}
// PriceHistoryPageResult 成本价历史分页结果
type PriceHistoryPageResult struct {
List []*PriceHistoryResponse `json:"list" description:"历史记录列表"`
Total int64 `json:"total" description:"总数"`
Page int `json:"page" description:"当前页"`
PageSize int `json:"page_size" description:"每页数量"`
TotalPages int `json:"total_pages" description:"总页数"`
}

View File

@@ -62,8 +62,8 @@ type MySeriesAllocationResponse struct {
SeriesID uint `json:"series_id" description:"套餐系列ID"`
SeriesCode string `json:"series_code" description:"系列编码"`
SeriesName string `json:"series_name" description:"系列名称"`
PricingMode string `json:"pricing_mode" description:"加价模式 (fixed:固定金额, percent:百分比)"`
PricingValue int64 `json:"pricing_value" description:"加价值"`
BaseCommissionMode string `json:"base_commission_mode" description:"基础佣金模式 (fixed:固定金额, percent:百分比)"`
BaseCommissionValue int64 `json:"base_commission_value" description:"基础佣金值"`
AvailablePackageCount int `json:"available_package_count" description:"可售套餐数量"`
AllocatorShopName string `json:"allocator_shop_name" description:"分配者店铺名称"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`

View File

@@ -52,25 +52,37 @@ type UpdatePackageShelfStatusRequest struct {
ShelfStatus int `json:"shelf_status" validate:"required,oneof=1 2" required:"true" description:"上架状态 (1:上架, 2:下架)"`
}
// CommissionTierInfo 返佣梯度信息
type CommissionTierInfo struct {
CurrentRate string `json:"current_rate" description:"当前返佣比例"`
NextThreshold *int64 `json:"next_threshold,omitempty" description:"下一档位阈值"`
NextRate string `json:"next_rate,omitempty" description:"下一档位返佣比例"`
}
// PackageResponse 套餐响应
type PackageResponse struct {
ID uint `json:"id" description:"套餐ID"`
PackageCode string `json:"package_code" description:"套餐编码"`
PackageName string `json:"package_name" description:"套餐名称"`
SeriesID *uint `json:"series_id" description:"套餐系列ID"`
PackageType string `json:"package_type" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
DurationMonths int `json:"duration_months" description:"套餐时长(月数)"`
DataType string `json:"data_type" description:"流量类型 (real:真流量, virtual:虚流量)"`
RealDataMB int64 `json:"real_data_mb" description:"流量额度(MB)"`
VirtualDataMB int64 `json:"virtual_data_mb" description:"流量额度(MB)"`
DataAmountMB int64 `json:"data_amount_mb" description:"流量额度(MB)"`
Price int64 `json:"price" description:"套餐价格(分)"`
SuggestedCostPrice int64 `json:"suggested_cost_price" description:"建议成本价(分)"`
SuggestedRetailPrice int64 `json:"suggested_retail_price" description:"建议价(分)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
ShelfStatus int `json:"shelf_status" description:"上架状态 (1:上架, 2:下架)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
ID uint `json:"id" description:"套餐ID"`
PackageCode string `json:"package_code" description:"套餐编码"`
PackageName string `json:"package_name" description:"套餐名称"`
SeriesID *uint `json:"series_id" description:"套餐系列ID"`
SeriesName *string `json:"series_name" description:"套餐系列名称"`
PackageType string `json:"package_type" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
DurationMonths int `json:"duration_months" description:"套餐时长(月数)"`
DataType string `json:"data_type" description:"流量类型 (real:真流量, virtual:虚流量)"`
RealDataMB int64 `json:"real_data_mb" description:"流量额度(MB)"`
VirtualDataMB int64 `json:"virtual_data_mb" description:"流量额度(MB)"`
DataAmountMB int64 `json:"data_amount_mb" description:"总流量额度(MB)"`
Price int64 `json:"price" description:"套餐价格(分)"`
SuggestedCostPrice int64 `json:"suggested_cost_price" description:"建议成本价(分)"`
SuggestedRetailPrice int64 `json:"suggested_retail_price" description:"建议售价(分)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
ShelfStatus int `json:"shelf_status" description:"上架状态 (1:上架, 2:下架)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
CostPrice *int64 `json:"cost_price,omitempty" description:"成本价(分,仅代理用户可见)"`
ProfitMargin *int64 `json:"profit_margin,omitempty" description:"利润空间(分,仅代理用户可见)"`
CurrentCommissionRate string `json:"current_commission_rate,omitempty" description:"当前返佣比例(仅代理用户可见)"`
TierInfo *CommissionTierInfo `json:"tier_info,omitempty" description:"梯度返佣信息(仅代理用户可见)"`
}
// UpdatePackageParams 更新套餐聚合参数

View File

@@ -0,0 +1,26 @@
package dto
// PriceAdjustment 价格调整配置
type PriceAdjustment struct {
Type string `json:"type" validate:"required,oneof=fixed percent" required:"true" description:"调整类型 (fixed:固定金额, percent:百分比)"`
Value int64 `json:"value" validate:"required" required:"true" description:"调整值(分或千分比)"`
}
// BatchAllocatePackagesRequest 批量分配套餐请求
type BatchAllocatePackagesRequest struct {
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
SeriesID uint `json:"series_id" validate:"required" required:"true" description:"套餐系列ID"`
PriceAdjustment *PriceAdjustment `json:"price_adjustment" validate:"omitempty" description:"可选加价配置"`
BaseCommission BaseCommissionConfig `json:"base_commission" validate:"required" required:"true" description:"基础返佣配置"`
EnableTierCommission bool `json:"enable_tier_commission" description:"是否启用梯度返佣"`
TierConfig *TierCommissionConfig `json:"tier_config" validate:"omitempty" description:"梯度返佣配置(启用梯度返佣时必填)"`
}
// BatchAllocatePackagesResponse 批量分配套餐响应
type BatchAllocatePackagesResponse struct {
AllocationID uint `json:"allocation_id" description:"系列分配ID"`
TotalPackages int `json:"total_packages" description:"总套餐数"`
AllocatedCount int `json:"allocated_count" description:"成功分配数量"`
SkippedCount int `json:"skipped_count" description:"跳过数量(已存在)"`
PackageIDs []uint `json:"package_ids" description:"分配的套餐ID列表"`
}

View File

@@ -0,0 +1,15 @@
package dto
// BatchUpdateCostPriceRequest 批量调价请求
type BatchUpdateCostPriceRequest struct {
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"店铺ID"`
SeriesID *uint `json:"series_id" validate:"omitempty" description:"套餐系列ID可选不填则调整所有"`
PriceAdjustment PriceAdjustment `json:"price_adjustment" validate:"required" required:"true" description:"价格调整配置"`
ChangeReason string `json:"change_reason" validate:"omitempty,max=255" maxLength:"255" description:"变更原因"`
}
// BatchUpdateCostPriceResponse 批量调价响应
type BatchUpdateCostPriceResponse struct {
UpdatedCount int `json:"updated_count" description:"更新数量"`
AffectedIDs []uint `json:"affected_ids" description:"受影响的分配ID列表"`
}

View File

@@ -1,23 +1,39 @@
package dto
// BaseCommissionConfig 基础返佣配置
type BaseCommissionConfig struct {
Mode string `json:"mode" validate:"required,oneof=fixed percent" required:"true" description:"返佣模式 (fixed:固定金额, percent:百分比)"`
Value int64 `json:"value" validate:"required,min=0" required:"true" minimum:"0" description:"返佣值分或千分比如200=20%"`
}
// TierCommissionConfig 梯度返佣配置
type TierCommissionConfig struct {
PeriodType string `json:"period_type" validate:"required,oneof=monthly quarterly yearly" required:"true" description:"周期类型 (monthly:月度, quarterly:季度, yearly:年度)"`
TierType string `json:"tier_type" validate:"required,oneof=sales_count sales_amount" required:"true" description:"梯度类型 (sales_count:销量, sales_amount:销售额)"`
Tiers []TierEntry `json:"tiers" validate:"required,min=1,dive" required:"true" description:"梯度档位列表"`
}
// TierEntry 梯度档位条目
type TierEntry struct {
Threshold int64 `json:"threshold" validate:"required,min=1" required:"true" minimum:"1" description:"阈值(销量或金额分)"`
Mode string `json:"mode" validate:"required,oneof=fixed percent" required:"true" description:"达标后返佣模式 (fixed:固定金额, percent:百分比)"`
Value int64 `json:"value" validate:"required,min=1" required:"true" minimum:"1" description:"达标后返佣值(分或千分比)"`
}
// CreateShopSeriesAllocationRequest 创建套餐系列分配请求
type CreateShopSeriesAllocationRequest struct {
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
SeriesID uint `json:"series_id" validate:"required" required:"true" description:"套餐系列ID"`
PricingMode string `json:"pricing_mode" validate:"required,oneof=fixed percent" required:"true" description:"加价模式 (fixed:固定金额, percent:百分比)"`
PricingValue int64 `json:"pricing_value" validate:"required,min=0" required:"true" minimum:"0" description:"加价值分或千分比如100=10%"`
OneTimeCommissionTrigger string `json:"one_time_commission_trigger" validate:"omitempty,oneof=one_time_recharge accumulated_recharge" description:"一次性佣金触发类型 (one_time_recharge:单次充值, accumulated_recharge:累计充值)"`
OneTimeCommissionThreshold int64 `json:"one_time_commission_threshold" validate:"omitempty,min=0" minimum:"0" description:"一次性佣金触发阈值(分)"`
OneTimeCommissionAmount int64 `json:"one_time_commission_amount" validate:"omitempty,min=0" minimum:"0" description:"一次性佣金金额(分)"`
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
SeriesID uint `json:"series_id" validate:"required" required:"true" description:"套餐系列ID"`
BaseCommission BaseCommissionConfig `json:"base_commission" validate:"required" required:"true" description:"基础返佣配置"`
EnableTierCommission bool `json:"enable_tier_commission" description:"是否启用梯度返佣"`
TierConfig *TierCommissionConfig `json:"tier_config" validate:"omitempty" description:"梯度返佣配置(启用梯度返佣时必填)"`
}
// UpdateShopSeriesAllocationRequest 更新套餐系列分配请求
type UpdateShopSeriesAllocationRequest struct {
PricingMode *string `json:"pricing_mode" validate:"omitempty,oneof=fixed percent" description:"加价模式 (fixed:固定金额, percent:百分比)"`
PricingValue *int64 `json:"pricing_value" validate:"omitempty,min=0" minimum:"0" description:"加价值(分或千分比)"`
OneTimeCommissionTrigger *string `json:"one_time_commission_trigger" validate:"omitempty,oneof=one_time_recharge accumulated_recharge" description:"一次性佣金触发类型"`
OneTimeCommissionThreshold *int64 `json:"one_time_commission_threshold" validate:"omitempty,min=0" minimum:"0" description:"一次性佣金触发阈值(分)"`
OneTimeCommissionAmount *int64 `json:"one_time_commission_amount" validate:"omitempty,min=0" minimum:"0" description:"一次性佣金金额(分)"`
BaseCommission *BaseCommissionConfig `json:"base_commission" validate:"omitempty" description:"基础返佣配置"`
EnableTierCommission *bool `json:"enable_tier_commission" description:"是否启用梯度返佣"`
TierConfig *TierCommissionConfig `json:"tier_config" validate:"omitempty" description:"梯度返佣配置"`
}
// ShopSeriesAllocationListRequest 套餐系列分配列表请求
@@ -36,22 +52,18 @@ type UpdateShopSeriesAllocationStatusRequest struct {
// ShopSeriesAllocationResponse 套餐系列分配响应
type ShopSeriesAllocationResponse struct {
ID uint `json:"id" description:"分配ID"`
ShopID uint `json:"shop_id" description:"被分配的店铺ID"`
ShopName string `json:"shop_name" description:"被分配的店铺名称"`
SeriesID uint `json:"series_id" description:"套餐系列ID"`
SeriesName string `json:"series_name" description:"套餐系列名称"`
AllocatorShopID uint `json:"allocator_shop_id" description:"分配者店铺ID"`
AllocatorShopName string `json:"allocator_shop_name" description:"分配者店铺名称"`
PricingMode string `json:"pricing_mode" description:"加价模式 (fixed:固定金额, percent:百分比)"`
PricingValue int64 `json:"pricing_value" description:"加价值(分或千分比)"`
CalculatedCostPrice int64 `json:"calculated_cost_price" description:"计算后的成本价(分)"`
OneTimeCommissionTrigger string `json:"one_time_commission_trigger" description:"一次性佣金触发类型"`
OneTimeCommissionThreshold int64 `json:"one_time_commission_threshold" description:"一次性佣金触发阈值(分)"`
OneTimeCommissionAmount int64 `json:"one_time_commission_amount" description:"一次性佣金金额(分)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
ID uint `json:"id" description:"分配ID"`
ShopID uint `json:"shop_id" description:"被分配的店铺ID"`
ShopName string `json:"shop_name" description:"被分配的店铺名称"`
SeriesID uint `json:"series_id" description:"套餐系列ID"`
SeriesName string `json:"series_name" description:"套餐系列名称"`
AllocatorShopID uint `json:"allocator_shop_id" description:"分配者店铺ID"`
AllocatorShopName string `json:"allocator_shop_name" description:"分配者店铺名称"`
BaseCommission BaseCommissionConfig `json:"base_commission" description:"基础返佣配置"`
EnableTierCommission bool `json:"enable_tier_commission" description:"是否启用梯度返佣"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// ShopSeriesAllocationPageResult 套餐系列分配分页结果
@@ -74,77 +86,3 @@ type UpdateShopSeriesAllocationStatusParams struct {
IDReq
UpdateShopSeriesAllocationStatusRequest
}
// CreateCommissionTierRequest 创建梯度佣金请求
type CreateCommissionTierRequest struct {
TierType string `json:"tier_type" validate:"required,oneof=sales_count sales_amount" required:"true" description:"梯度类型 (sales_count:销量, sales_amount:销售额)"`
PeriodType string `json:"period_type" validate:"required,oneof=monthly quarterly yearly custom" required:"true" description:"周期类型 (monthly:月度, quarterly:季度, yearly:年度, custom:自定义)"`
PeriodStartDate *string `json:"period_start_date" validate:"omitempty" description:"自定义周期开始日期(YYYY-MM-DD)当周期类型为custom时必填"`
PeriodEndDate *string `json:"period_end_date" validate:"omitempty" description:"自定义周期结束日期(YYYY-MM-DD)当周期类型为custom时必填"`
ThresholdValue int64 `json:"threshold_value" validate:"required,min=1" required:"true" minimum:"1" description:"阈值(销量或金额分)"`
CommissionAmount int64 `json:"commission_amount" validate:"required,min=1" required:"true" minimum:"1" description:"佣金金额(分)"`
}
// UpdateCommissionTierRequest 更新梯度佣金请求
type UpdateCommissionTierRequest struct {
TierType *string `json:"tier_type" validate:"omitempty,oneof=sales_count sales_amount" description:"梯度类型"`
PeriodType *string `json:"period_type" validate:"omitempty,oneof=monthly quarterly yearly custom" description:"周期类型"`
PeriodStartDate *string `json:"period_start_date" validate:"omitempty" description:"自定义周期开始日期"`
PeriodEndDate *string `json:"period_end_date" validate:"omitempty" description:"自定义周期结束日期"`
ThresholdValue *int64 `json:"threshold_value" validate:"omitempty,min=1" minimum:"1" description:"阈值"`
CommissionAmount *int64 `json:"commission_amount" validate:"omitempty,min=1" minimum:"1" description:"佣金金额(分)"`
}
// CommissionTierResponse 梯度佣金响应
type CommissionTierResponse struct {
ID uint `json:"id" description:"梯度ID"`
AllocationID uint `json:"allocation_id" description:"关联的分配ID"`
TierType string `json:"tier_type" description:"梯度类型 (sales_count:销量, sales_amount:销售额)"`
PeriodType string `json:"period_type" description:"周期类型 (monthly:月度, quarterly:季度, yearly:年度, custom:自定义)"`
PeriodStartDate string `json:"period_start_date,omitempty" description:"自定义周期开始日期"`
PeriodEndDate string `json:"period_end_date,omitempty" description:"自定义周期结束日期"`
ThresholdValue int64 `json:"threshold_value" description:"阈值"`
CommissionAmount int64 `json:"commission_amount" description:"佣金金额(分)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// CreateCommissionTierParams 创建梯度佣金聚合参数
type CreateCommissionTierParams struct {
IDReq
CreateCommissionTierRequest
}
// UpdateCommissionTierParams 更新梯度佣金聚合参数
type UpdateCommissionTierParams struct {
AllocationIDReq
TierIDReq
UpdateCommissionTierRequest
}
// DeleteCommissionTierParams 删除梯度佣金聚合参数
type DeleteCommissionTierParams struct {
AllocationIDReq
TierIDReq
}
// AllocationIDReq 分配ID路径参数
type AllocationIDReq struct {
ID uint `path:"id" description:"分配ID" required:"true"`
}
// TierIDReq 梯度ID路径参数
type TierIDReq struct {
TierID uint `path:"tier_id" description:"梯度ID" required:"true"`
}
// CommissionTierListResult 梯度佣金列表结果
type CommissionTierListResult struct {
List []*CommissionTierResponse `json:"list" description:"梯度佣金列表"`
}
// TierIDParams 梯度ID路径参数组合
type TierIDParams struct {
AllocationIDReq
TierIDReq
}