清理冗余的梯度返佣(TierCommission)配置
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m46s

- 移除 Model 层:删除 ShopSeriesCommissionTier 模型及相关字段
- 更新 DTO:删除 TierCommissionConfig、TierEntry 类型及相关请求/响应字段
- 删除 Store 层:移除 ShopSeriesCommissionTierStore 及相关查询逻辑
- 简化 Service 层:删除梯度返佣处理逻辑,统计查询移除 tier_bonus 字段
- 数据库迁移:创建 000034_remove_tier_commission 移除相关表和字段
- 更新测试:移除梯度返佣相关测试用例,更新集成测试
- OpenAPI 文档:删除梯度返佣相关 schema 和枚举值
- 归档变更:归档 remove-tier-commission-redundancy 到 archive/2026-01-30-
- 同步规范:更新 4 个主 specs,标记废弃功能并添加迁移指引

原因:梯度返佣功能与一次性梯度佣金功能重复,且从未实现实际计算逻辑
迁移:使用一次性佣金的梯度模式 (OneTimeCommissionConfig.type = "tiered") 替代
This commit is contained in:
2026-01-30 14:57:24 +08:00
parent 409a68d60b
commit 1cf17e8f14
39 changed files with 978 additions and 407 deletions

View File

@@ -112,10 +112,10 @@ func initServices(s *stores, deps *Dependencies) *services {
AssetAllocationRecord: assetAllocationRecordSvc.New(deps.DB, s.AssetAllocationRecord, s.Shop, s.Account),
Carrier: carrierSvc.New(s.Carrier),
PackageSeries: packageSeriesSvc.New(s.PackageSeries),
Package: packageSvc.New(s.Package, s.PackageSeries, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.ShopSeriesCommissionTier),
ShopSeriesAllocation: shopSeriesAllocationSvc.New(s.ShopSeriesAllocation, s.ShopSeriesCommissionTier, s.ShopSeriesAllocationConfig, s.ShopSeriesOneTimeCommissionTier, s.Shop, s.PackageSeries, s.Package),
Package: packageSvc.New(s.Package, s.PackageSeries, s.ShopPackageAllocation, s.ShopSeriesAllocation),
ShopSeriesAllocation: shopSeriesAllocationSvc.New(s.ShopSeriesAllocation, s.ShopSeriesAllocationConfig, s.ShopSeriesOneTimeCommissionTier, s.Shop, s.PackageSeries, s.Package),
ShopPackageAllocation: shopPackageAllocationSvc.New(s.ShopPackageAllocation, s.ShopSeriesAllocation, s.ShopPackageAllocationPriceHistory, s.Shop, s.Package),
ShopPackageBatchAllocation: shopPackageBatchAllocationSvc.New(deps.DB, s.Package, s.ShopSeriesAllocation, s.ShopPackageAllocation, s.ShopSeriesAllocationConfig, s.ShopSeriesCommissionTier, s.ShopSeriesCommissionStats, s.Shop),
ShopPackageBatchAllocation: shopPackageBatchAllocationSvc.New(deps.DB, s.Package, s.ShopSeriesAllocation, s.ShopPackageAllocation, s.ShopSeriesAllocationConfig, s.ShopSeriesCommissionStats, s.Shop),
ShopPackageBatchPricing: shopPackageBatchPricingSvc.New(deps.DB, s.ShopPackageAllocation, s.ShopPackageAllocationPriceHistory, s.Shop),
CommissionStats: commissionStatsSvc.New(s.ShopSeriesCommissionStats),
PurchaseValidation: purchaseValidation,

View File

@@ -31,7 +31,6 @@ type stores struct {
PackageSeries *postgres.PackageSeriesStore
Package *postgres.PackageStore
ShopSeriesAllocation *postgres.ShopSeriesAllocationStore
ShopSeriesCommissionTier *postgres.ShopSeriesCommissionTierStore
ShopSeriesOneTimeCommissionTier *postgres.ShopSeriesOneTimeCommissionTierStore
ShopSeriesAllocationConfig *postgres.ShopSeriesAllocationConfigStore
ShopPackageAllocation *postgres.ShopPackageAllocationStore
@@ -69,7 +68,6 @@ func initStores(deps *Dependencies) *stores {
PackageSeries: postgres.NewPackageSeriesStore(deps.DB),
Package: postgres.NewPackageStore(deps.DB),
ShopSeriesAllocation: postgres.NewShopSeriesAllocationStore(deps.DB),
ShopSeriesCommissionTier: postgres.NewShopSeriesCommissionTierStore(deps.DB),
ShopSeriesOneTimeCommissionTier: postgres.NewShopSeriesOneTimeCommissionTierStore(deps.DB),
ShopSeriesAllocationConfig: postgres.NewShopSeriesAllocationConfigStore(deps.DB),
ShopPackageAllocation: postgres.NewShopPackageAllocationStore(deps.DB),

View File

@@ -8,7 +8,7 @@ import (
// CommissionRecord 佣金记录模型
// 记录各级代理的佣金入账情况
// 包含成本价差收入一次性佣金、梯度奖励等多种佣金来源
// 包含成本价差收入一次性佣金种佣金来源
type CommissionRecord struct {
gorm.Model
BaseModel `gorm:"embedded"`
@@ -16,7 +16,7 @@ type CommissionRecord struct {
OrderID uint `gorm:"column:order_id;index;not null;comment:订单ID" json:"order_id"`
IotCardID *uint `gorm:"column:iot_card_id;index;comment:关联卡ID可空" json:"iot_card_id"`
DeviceID *uint `gorm:"column:device_id;index;comment:关联设备ID可空" json:"device_id"`
CommissionSource string `gorm:"column:commission_source;type:varchar(20);not null;index;comment:佣金来源 cost_diff-成本价差 one_time-一次性佣金 tier_bonus-梯度奖励" json:"commission_source"`
CommissionSource string `gorm:"column:commission_source;type:varchar(20);not null;index;comment:佣金来源 cost_diff-成本价差 one_time-一次性佣金" json:"commission_source"`
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:佣金金额(分)" json:"amount"`
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;default:0;comment:入账后钱包余额(分)" json:"balance_after"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-已入账 2-已失效" json:"status"`
@@ -35,8 +35,6 @@ const (
CommissionSourceCostDiff = "cost_diff"
// CommissionSourceOneTime 一次性佣金
CommissionSourceOneTime = "one_time"
// CommissionSourceTierBonus 梯度奖励
CommissionSourceTierBonus = "tier_bonus"
)
// 佣金状态常量

View File

@@ -2,15 +2,14 @@ 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:"创建时间"`
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:"基础返佣值(分或千分比)"`
EffectiveFrom string `json:"effective_from" description:"生效开始时间"`
EffectiveTo string `json:"effective_to,omitempty" description:"生效结束时间NULL表示当前生效"`
CreatedAt string `json:"created_at" description:"创建时间"`
}
// AllocationConfigListResponse 配置版本列表响应

View File

@@ -11,7 +11,7 @@ type CommissionRecordResponse struct {
IotCardICCID string `json:"iot_card_iccid" description:"卡ICCID"`
DeviceID *uint `json:"device_id" description:"关联设备ID"`
DeviceNo string `json:"device_no" description:"设备号"`
CommissionSource string `json:"commission_source" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励)"`
CommissionSource string `json:"commission_source" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金)"`
Amount int64 `json:"amount" description:"佣金金额(分)"`
BalanceAfter int64 `json:"balance_after" description:"入账后钱包余额(分)"`
Status int `json:"status" description:"状态 (1:已入账, 2:已失效)"`
@@ -25,7 +25,7 @@ type CommissionRecordListRequest 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:"每页数量"`
ShopID *uint `json:"shop_id" query:"shop_id" validate:"omitempty" description:"店铺ID"`
CommissionSource *string `json:"commission_source" query:"commission_source" validate:"omitempty,oneof=cost_diff one_time tier_bonus" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励)"`
CommissionSource *string `json:"commission_source" query:"commission_source" validate:"omitempty,oneof=cost_diff one_time" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金)"`
StartTime *string `json:"start_time" query:"start_time" validate:"omitempty" description:"开始时间"`
EndTime *string `json:"end_time" query:"end_time" validate:"omitempty" description:"结束时间"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:已入账, 2:已失效)"`
@@ -42,17 +42,14 @@ type CommissionRecordPageResult struct {
// CommissionStatsResponse 佣金统计响应
type CommissionStatsResponse struct {
TotalAmount int64 `json:"total_amount" description:"总收入(分)"`
CostDiffAmount int64 `json:"cost_diff_amount" description:"成本价差收入(分)"`
OneTimeAmount int64 `json:"one_time_amount" description:"一次性佣金收入(分)"`
TierBonusAmount int64 `json:"tier_bonus_amount" description:"梯度奖励收入(分"`
CostDiffPercent int64 `json:"cost_diff_percent" description:"成本价差占比(千分比)"`
OneTimePercent int64 `json:"one_time_percent" description:"一次性佣金占比(千分比)"`
TierBonusPercent int64 `json:"tier_bonus_percent" description:"梯度奖励占比(千分比)"`
TotalCount int64 `json:"total_count" description:"笔数"`
CostDiffCount int64 `json:"cost_diff_count" description:"成本价差笔数"`
OneTimeCount int64 `json:"one_time_count" description:"一次性佣金笔数"`
TierBonusCount int64 `json:"tier_bonus_count" description:"梯度奖励笔数"`
TotalAmount int64 `json:"total_amount" description:"总收入(分)"`
CostDiffAmount int64 `json:"cost_diff_amount" description:"成本价差收入(分)"`
OneTimeAmount int64 `json:"one_time_amount" description:"一次性佣金收入(分)"`
CostDiffPercent int64 `json:"cost_diff_percent" description:"成本价差占比(千分比"`
OneTimePercent int64 `json:"one_time_percent" description:"一次性佣金占比(千分比)"`
TotalCount int64 `json:"total_count" description:"总笔数"`
CostDiffCount int64 `json:"cost_diff_count" description:"成本价差笔数"`
OneTimeCount int64 `json:"one_time_count" description:"一次性佣金笔数"`
}
// DailyCommissionStatsResponse 每日佣金统计响应

View File

@@ -41,7 +41,7 @@ type MyWithdrawalListReq struct {
type MyCommissionRecordListReq 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:"每页数量"`
CommissionSource *string `json:"commission_source" query:"commission_source" validate:"omitempty,oneof=cost_diff one_time tier_bonus" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励)"`
CommissionSource *string `json:"commission_source" query:"commission_source" validate:"omitempty,oneof=cost_diff one_time tier_bonus" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励)"`
ICCID string `json:"iccid" query:"iccid" description:"ICCID模糊查询"`
DeviceNo string `json:"device_no" query:"device_no" description:"设备号(模糊查询)"`
OrderNo string `json:"order_no" query:"order_no" description:"订单号(模糊查询)"`
@@ -51,7 +51,7 @@ type MyCommissionRecordItem struct {
ID uint `json:"id" description:"佣金记录ID"`
ShopID uint `json:"shop_id" description:"店铺ID"`
OrderID uint `json:"order_id" description:"订单ID"`
CommissionSource string `json:"commission_source" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励)"`
CommissionSource string `json:"commission_source" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励)"`
Amount int64 `json:"amount" description:"佣金金额(分)"`
Status int `json:"status" description:"状态 (1:已入账, 2:已失效)"`
StatusName string `json:"status_name" description:"状态名称"`

View File

@@ -96,7 +96,7 @@ type ShopCommissionRecordListReq struct {
ShopID uint `json:"-" params:"shop_id" path:"shop_id" validate:"required" description:"店铺ID"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码默认1"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量默认20最大100"`
CommissionSource string `json:"commission_source" query:"commission_source" validate:"omitempty,oneof=cost_diff one_time tier_bonus" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励)"`
CommissionSource string `json:"commission_source" query:"commission_source" validate:"omitempty,oneof=cost_diff one_time tier_bonus" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励)"`
ICCID string `json:"iccid" query:"iccid" validate:"omitempty,max=50" maxLength:"50" description:"ICCID模糊查询"`
DeviceNo string `json:"device_no" query:"device_no" validate:"omitempty,max=50" maxLength:"50" description:"设备号(模糊查询)"`
OrderNo string `json:"order_no" query:"order_no" validate:"omitempty,max=50" maxLength:"50" description:"订单号(模糊查询)"`
@@ -107,7 +107,7 @@ type ShopCommissionRecordItem struct {
ID uint `json:"id" description:"佣金记录ID"`
Amount int64 `json:"amount" description:"佣金金额(分)"`
BalanceAfter int64 `json:"balance_after" description:"入账后佣金余额(分)"`
CommissionSource string `json:"commission_source" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励)"`
CommissionSource string `json:"commission_source" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励)"`
Status int `json:"status" description:"状态 (1:已入账, 2:已失效)"`
StatusName string `json:"status_name" description:"状态名称"`
OrderID uint `json:"order_id" description:"订单ID"`

View File

@@ -8,12 +8,10 @@ type PriceAdjustment struct {
// 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:"梯度返佣配置(启用梯度返佣时必填)"`
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:"基础返佣配置"`
}
// BatchAllocatePackagesResponse 批量分配套餐响应

View File

@@ -6,20 +6,6 @@ type BaseCommissionConfig struct {
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:"达标后返佣值(分或千分比)"`
}
// OneTimeCommissionConfig 一次性佣金配置
type OneTimeCommissionConfig struct {
Type string `json:"type" validate:"required,oneof=fixed tiered" required:"true" description:"一次性佣金类型 (fixed:固定, tiered:梯度)"`
@@ -43,8 +29,6 @@ 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"`
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:"梯度返佣配置(启用梯度返佣时必填)"`
EnableOneTimeCommission bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionConfig *OneTimeCommissionConfig `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金配置(启用一次性佣金时必填)"`
}
@@ -52,8 +36,6 @@ type CreateShopSeriesAllocationRequest struct {
// UpdateShopSeriesAllocationRequest 更新套餐系列分配请求
type UpdateShopSeriesAllocationRequest struct {
BaseCommission *BaseCommissionConfig `json:"base_commission" validate:"omitempty" description:"基础返佣配置"`
EnableTierCommission *bool `json:"enable_tier_commission" description:"是否启用梯度返佣"`
TierConfig *TierCommissionConfig `json:"tier_config" validate:"omitempty" description:"梯度返佣配置"`
EnableOneTimeCommission *bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionConfig *OneTimeCommissionConfig `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金配置"`
}
@@ -82,7 +64,6 @@ type ShopSeriesAllocationResponse struct {
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:"是否启用梯度返佣"`
EnableOneTimeCommission bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionConfig *OneTimeCommissionConfig `json:"one_time_commission_config,omitempty" description:"一次性佣金配置"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`

View File

@@ -9,13 +9,12 @@ import (
// 分配者只能分配自己已被分配的套餐系列,且只能分配给直属下级
type ShopSeriesAllocation struct {
gorm.Model
BaseModel `gorm:"embedded"`
ShopID uint `gorm:"column:shop_id;index;not null;comment:被分配的店铺ID" json:"shop_id"`
SeriesID uint `gorm:"column:series_id;index;not null;comment:套餐系列ID" json:"series_id"`
AllocatorShopID uint `gorm:"column:allocator_shop_id;index;not null;comment:分配者店铺ID(上级)" json:"allocator_shop_id"`
BaseCommissionMode string `gorm:"column:base_commission_mode;type:varchar(20);not null;default:percent;comment:基础返佣模式 fixed-固定金额 percent-百分比" json:"base_commission_mode"`
BaseCommissionValue int64 `gorm:"column:base_commission_value;type:bigint;not null;default:0;comment:基础返佣值(分或千分比,如200=20%)" json:"base_commission_value"`
EnableTierCommission bool `gorm:"column:enable_tier_commission;type:boolean;not null;default:false;comment:是否启用梯度返佣" json:"enable_tier_commission"`
BaseModel `gorm:"embedded"`
ShopID uint `gorm:"column:shop_id;index;not null;comment:被分配的店铺ID" json:"shop_id"`
SeriesID uint `gorm:"column:series_id;index;not null;comment:套餐系列ID" json:"series_id"`
AllocatorShopID uint `gorm:"column:allocator_shop_id;index;not null;comment:分配者店铺ID(上级)" json:"allocator_shop_id"`
BaseCommissionMode string `gorm:"column:base_commission_mode;type:varchar(20);not null;default:percent;comment:基础返佣模式 fixed-固定金额 percent-百分比" json:"base_commission_mode"`
BaseCommissionValue int64 `gorm:"column:base_commission_value;type:bigint;not null;default:0;comment:基础返佣值(分或千分比,如200=20%)" json:"base_commission_value"`
// 一次性佣金配置
EnableOneTimeCommission bool `gorm:"column:enable_one_time_commission;type:boolean;not null;default:false;comment:是否启用一次性佣金" json:"enable_one_time_commission"`

View File

@@ -11,13 +11,12 @@ import (
// 支持配置追溯和数据一致性保障
type ShopSeriesAllocationConfig struct {
gorm.Model
AllocationID uint `gorm:"column:allocation_id;index;not null;comment:关联的分配ID" json:"allocation_id"`
Version int `gorm:"column:version;type:int;not null;comment:配置版本号" json:"version"`
BaseCommissionMode string `gorm:"column:base_commission_mode;type:varchar(20);not null;comment:基础返佣模式(配置快照)" json:"base_commission_mode"`
BaseCommissionValue int64 `gorm:"column:base_commission_value;type:bigint;not null;comment:基础返佣值(配置快照)" json:"base_commission_value"`
EnableTierCommission bool `gorm:"column:enable_tier_commission;type:boolean;not null;comment:是否启用梯度返佣(配置快照)" json:"enable_tier_commission"`
EffectiveFrom time.Time `gorm:"column:effective_from;type:timestamptz;not null;comment:生效开始时间" json:"effective_from"`
EffectiveTo *time.Time `gorm:"column:effective_to;type:timestamptz;comment:生效结束时间(NULL表示当前生效)" json:"effective_to"`
AllocationID uint `gorm:"column:allocation_id;index;not null;comment:关联的分配ID" json:"allocation_id"`
Version int `gorm:"column:version;type:int;not null;comment:配置版本号" json:"version"`
BaseCommissionMode string `gorm:"column:base_commission_mode;type:varchar(20);not null;comment:基础返佣模式(配置快照)" json:"base_commission_mode"`
BaseCommissionValue int64 `gorm:"column:base_commission_value;type:bigint;not null;comment:基础返佣值(配置快照)" json:"base_commission_value"`
EffectiveFrom time.Time `gorm:"column:effective_from;type:timestamptz;not null;comment:生效开始时间" json:"effective_from"`
EffectiveTo *time.Time `gorm:"column:effective_to;type:timestamptz;comment:生效结束时间(NULL表示当前生效)" json:"effective_to"`
}
// TableName 指定表名

View File

@@ -1,48 +0,0 @@
package model
import (
"time"
"gorm.io/gorm"
)
// ShopSeriesCommissionTier 梯度佣金配置模型
// 基于销量或销售额配置不同档位的返佣比例提升
// 支持月度、季度、年度、自定义周期的统计
type ShopSeriesCommissionTier struct {
gorm.Model
BaseModel `gorm:"embedded"`
AllocationID uint `gorm:"column:allocation_id;index;not null;comment:关联的分配ID" json:"allocation_id"`
TierType string `gorm:"column:tier_type;type:varchar(20);not null;comment:梯度类型 sales_count-销量 sales_amount-销售额" json:"tier_type"`
PeriodType string `gorm:"column:period_type;type:varchar(20);not null;comment:周期类型 monthly-月度 quarterly-季度 yearly-年度 custom-自定义" json:"period_type"`
PeriodStartDate *time.Time `gorm:"column:period_start_date;comment:自定义周期开始日期" json:"period_start_date"`
PeriodEndDate *time.Time `gorm:"column:period_end_date;comment:自定义周期结束日期" json:"period_end_date"`
ThresholdValue int64 `gorm:"column:threshold_value;type:bigint;not null;comment:阈值(销量或金额分)" json:"threshold_value"`
CommissionMode string `gorm:"column:commission_mode;type:varchar(20);not null;default:percent;comment:达标后返佣模式 fixed-固定金额 percent-百分比" json:"commission_mode"`
CommissionValue int64 `gorm:"column:commission_value;type:bigint;not null;comment:达标后返佣值(分或千分比)" json:"commission_value"`
}
// TableName 指定表名
func (ShopSeriesCommissionTier) TableName() string {
return "tb_shop_series_commission_tier"
}
// 梯度类型常量
const (
// TierTypeSalesCount 销量梯度
TierTypeSalesCount = "sales_count"
// TierTypeSalesAmount 销售额梯度
TierTypeSalesAmount = "sales_amount"
)
// 周期类型常量
const (
// PeriodTypeMonthly 月度
PeriodTypeMonthly = "monthly"
// PeriodTypeQuarterly 季度
PeriodTypeQuarterly = "quarterly"
// PeriodTypeYearly 年度
PeriodTypeYearly = "yearly"
// PeriodTypeCustom 自定义
PeriodTypeCustom = "custom"
)

View File

@@ -23,12 +23,14 @@ func (ShopSeriesOneTimeCommissionTier) TableName() string {
return "tb_shop_series_one_time_commission_tier"
}
// 梯度类型常量(复用 ShopSeriesCommissionTier 的常量)
// TierTypeSalesCount = "sales_count" // 销量梯度
// TierTypeSalesAmount = "sales_amount" // 销售额梯度
// 这些常量已在 shop_series_commission_tier.go 中定义
// 梯度类型常量
const (
// TierTypeSalesCount 销量梯度
TierTypeSalesCount = "sales_count"
// TierTypeSalesAmount 销售额梯度
TierTypeSalesAmount = "sales_amount"
)
// 返佣模式常量(复用 ShopSeriesAllocation 的常量)
// CommissionModeFixed = "fixed" // 固定金额返佣
// CommissionModePercent = "percent" // 百分比返佣(千分比)
// 这些常量已在 shop_series_allocation.go 中定义
// 返佣模式常量在 shop_series_allocation.go 中定义
// CommissionModeFixed = "fixed"
// CommissionModePercent = "percent"

View File

@@ -387,25 +387,21 @@ func (s *Service) GetStats(ctx context.Context, req *dto.CommissionStatsRequest)
return &dto.CommissionStatsResponse{}, nil
}
var costDiffPercent, oneTimePercent, tierBonusPercent int64
var costDiffPercent, oneTimePercent int64
if stats.TotalAmount > 0 {
costDiffPercent = stats.CostDiffAmount * 1000 / stats.TotalAmount
oneTimePercent = stats.OneTimeAmount * 1000 / stats.TotalAmount
tierBonusPercent = stats.TierBonusAmount * 1000 / stats.TotalAmount
}
return &dto.CommissionStatsResponse{
TotalAmount: stats.TotalAmount,
CostDiffAmount: stats.CostDiffAmount,
OneTimeAmount: stats.OneTimeAmount,
TierBonusAmount: stats.TierBonusAmount,
CostDiffPercent: costDiffPercent,
OneTimePercent: oneTimePercent,
TierBonusPercent: tierBonusPercent,
TotalCount: stats.TotalCount,
CostDiffCount: stats.CostDiffCount,
OneTimeCount: stats.OneTimeCount,
TierBonusCount: stats.TierBonusCount,
TotalAmount: stats.TotalAmount,
CostDiffAmount: stats.CostDiffAmount,
OneTimeAmount: stats.OneTimeAmount,
CostDiffPercent: costDiffPercent,
OneTimePercent: oneTimePercent,
TotalCount: stats.TotalCount,
CostDiffCount: stats.CostDiffCount,
OneTimeCount: stats.OneTimeCount,
}, nil
}

View File

@@ -21,7 +21,6 @@ type Service struct {
packageSeriesStore *postgres.PackageSeriesStore
packageAllocationStore *postgres.ShopPackageAllocationStore
seriesAllocationStore *postgres.ShopSeriesAllocationStore
commissionTierStore *postgres.ShopSeriesCommissionTierStore
}
func New(
@@ -29,14 +28,12 @@ func New(
packageSeriesStore *postgres.PackageSeriesStore,
packageAllocationStore *postgres.ShopPackageAllocationStore,
seriesAllocationStore *postgres.ShopSeriesAllocationStore,
commissionTierStore *postgres.ShopSeriesCommissionTierStore,
) *Service {
return &Service{
packageStore: packageStore,
packageSeriesStore: packageSeriesStore,
packageAllocationStore: packageAllocationStore,
seriesAllocationStore: seriesAllocationStore,
commissionTierStore: commissionTierStore,
}
}
@@ -404,20 +401,5 @@ func (s *Service) getCommissionInfo(ctx context.Context, allocationID uint) *dto
info.CurrentRate = fmt.Sprintf("%.1f%%", float64(seriesAllocation.BaseCommissionValue)/10)
}
if seriesAllocation.EnableTierCommission {
tiers, err := s.commissionTierStore.ListByAllocationID(ctx, allocationID)
if err == nil && len(tiers) > 0 {
tier := tiers[0]
info.NextThreshold = &tier.ThresholdValue
if tier.CommissionMode == constants.CommissionModeFixed {
nextRate := fmt.Sprintf("%.2f元/单", float64(tier.CommissionValue)/100)
info.NextRate = nextRate
} else {
nextRate := fmt.Sprintf("%.1f%%", float64(tier.CommissionValue)/10)
info.NextRate = nextRate
}
}
}
return info
}

View File

@@ -25,7 +25,7 @@ func TestPackageService_Create(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
@@ -98,7 +98,7 @@ func TestPackageService_UpdateStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
@@ -168,7 +168,7 @@ func TestPackageService_UpdateShelfStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
@@ -255,7 +255,7 @@ func TestPackageService_Get(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
@@ -293,7 +293,7 @@ func TestPackageService_Update(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
@@ -342,7 +342,7 @@ func TestPackageService_Delete(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
@@ -377,7 +377,7 @@ func TestPackageService_List(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
@@ -460,7 +460,7 @@ func TestPackageService_SeriesNameInResponse(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
svc := New(packageStore, packageSeriesStore, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,

View File

@@ -19,7 +19,6 @@ type Service struct {
seriesAllocationStore *postgres.ShopSeriesAllocationStore
packageAllocationStore *postgres.ShopPackageAllocationStore
configStore *postgres.ShopSeriesAllocationConfigStore
commissionTierStore *postgres.ShopSeriesCommissionTierStore
commissionStatsStore *postgres.ShopSeriesCommissionStatsStore
shopStore *postgres.ShopStore
}
@@ -30,7 +29,6 @@ func New(
seriesAllocationStore *postgres.ShopSeriesAllocationStore,
packageAllocationStore *postgres.ShopPackageAllocationStore,
configStore *postgres.ShopSeriesAllocationConfigStore,
commissionTierStore *postgres.ShopSeriesCommissionTierStore,
commissionStatsStore *postgres.ShopSeriesCommissionStatsStore,
shopStore *postgres.ShopStore,
) *Service {
@@ -40,7 +38,6 @@ func New(
seriesAllocationStore: seriesAllocationStore,
packageAllocationStore: packageAllocationStore,
configStore: configStore,
commissionTierStore: commissionTierStore,
commissionStatsStore: commissionStatsStore,
shopStore: shopStore,
}
@@ -84,14 +81,13 @@ func (s *Service) BatchAllocate(ctx context.Context, req *dto.BatchAllocatePacka
return s.db.Transaction(func(tx *gorm.DB) error {
seriesAllocation := &model.ShopSeriesAllocation{
BaseModel: model.BaseModel{Creator: currentUserID, Updater: currentUserID},
ShopID: req.ShopID,
SeriesID: req.SeriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: req.BaseCommission.Mode,
BaseCommissionValue: req.BaseCommission.Value,
EnableTierCommission: req.EnableTierCommission,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{Creator: currentUserID, Updater: currentUserID},
ShopID: req.ShopID,
SeriesID: req.SeriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: req.BaseCommission.Mode,
BaseCommissionValue: req.BaseCommission.Value,
Status: constants.StatusEnabled,
}
if err := tx.Create(seriesAllocation).Error; err != nil {
@@ -100,12 +96,11 @@ func (s *Service) BatchAllocate(ctx context.Context, req *dto.BatchAllocatePacka
now := time.Now()
config := &model.ShopSeriesAllocationConfig{
AllocationID: seriesAllocation.ID,
Version: 1,
BaseCommissionMode: req.BaseCommission.Mode,
BaseCommissionValue: req.BaseCommission.Value,
EnableTierCommission: req.EnableTierCommission,
EffectiveFrom: now,
AllocationID: seriesAllocation.ID,
Version: 1,
BaseCommissionMode: req.BaseCommission.Mode,
BaseCommissionValue: req.BaseCommission.Value,
EffectiveFrom: now,
}
if err := tx.Create(config).Error; err != nil {
@@ -134,12 +129,6 @@ func (s *Service) BatchAllocate(ctx context.Context, req *dto.BatchAllocatePacka
return errors.Wrap(errors.CodeInternalError, err, "批量创建套餐分配失败")
}
if req.EnableTierCommission && req.TierConfig != nil {
if err := s.createCommissionTiers(tx, seriesAllocation.ID, req.TierConfig, currentUserID); err != nil {
return err
}
}
return nil
})
}
@@ -170,23 +159,3 @@ func (s *Service) calculateAdjustedPrice(basePrice int64, adjustment *dto.PriceA
return basePrice + (basePrice * adjustment.Value / 1000)
}
func (s *Service) createCommissionTiers(tx *gorm.DB, allocationID uint, config *dto.TierCommissionConfig, creatorID uint) error {
for _, tierReq := range config.Tiers {
tier := &model.ShopSeriesCommissionTier{
BaseModel: model.BaseModel{Creator: creatorID, Updater: creatorID},
AllocationID: allocationID,
PeriodType: config.PeriodType,
TierType: config.TierType,
ThresholdValue: tierReq.Threshold,
CommissionMode: tierReq.Mode,
CommissionValue: tierReq.Value,
}
if err := tx.Create(tier).Error; err != nil {
return errors.Wrap(errors.CodeInternalError, err, "创建佣金梯度失败")
}
}
return nil
}

View File

@@ -16,7 +16,6 @@ import (
type Service struct {
allocationStore *postgres.ShopSeriesAllocationStore
tierStore *postgres.ShopSeriesCommissionTierStore
configStore *postgres.ShopSeriesAllocationConfigStore
oneTimeCommissionTierStore *postgres.ShopSeriesOneTimeCommissionTierStore
shopStore *postgres.ShopStore
@@ -26,7 +25,6 @@ type Service struct {
func New(
allocationStore *postgres.ShopSeriesAllocationStore,
tierStore *postgres.ShopSeriesCommissionTierStore,
configStore *postgres.ShopSeriesAllocationConfigStore,
oneTimeCommissionTierStore *postgres.ShopSeriesOneTimeCommissionTierStore,
shopStore *postgres.ShopStore,
@@ -35,7 +33,6 @@ func New(
) *Service {
return &Service{
allocationStore: allocationStore,
tierStore: tierStore,
configStore: configStore,
oneTimeCommissionTierStore: oneTimeCommissionTierStore,
shopStore: shopStore,
@@ -106,13 +103,12 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesAllocatio
}
allocation := &model.ShopSeriesAllocation{
ShopID: req.ShopID,
SeriesID: req.SeriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: req.BaseCommission.Mode,
BaseCommissionValue: req.BaseCommission.Value,
EnableTierCommission: req.EnableTierCommission,
Status: constants.StatusEnabled,
ShopID: req.ShopID,
SeriesID: req.SeriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: req.BaseCommission.Mode,
BaseCommissionValue: req.BaseCommission.Value,
Status: constants.StatusEnabled,
}
// 处理一次性佣金配置
@@ -192,12 +188,6 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateShopSeries
allocation.BaseCommissionMode = req.BaseCommission.Mode
allocation.BaseCommissionValue = req.BaseCommission.Value
}
if req.EnableTierCommission != nil {
if allocation.EnableTierCommission != *req.EnableTierCommission {
configChanged = true
}
allocation.EnableTierCommission = *req.EnableTierCommission
}
enableOneTimeCommission := allocation.EnableOneTimeCommission
if req.EnableOneTimeCommission != nil {
@@ -409,7 +399,6 @@ func (s *Service) buildResponse(ctx context.Context, a *model.ShopSeriesAllocati
Mode: a.BaseCommissionMode,
Value: a.BaseCommissionValue,
},
EnableTierCommission: a.EnableTierCommission,
EnableOneTimeCommission: a.EnableOneTimeCommission,
Status: a.Status,
CreatedAt: a.CreatedAt.Format(time.RFC3339),
@@ -458,12 +447,11 @@ func (s *Service) createNewConfigVersion(ctx context.Context, allocation *model.
}
newConfig := &model.ShopSeriesAllocationConfig{
AllocationID: allocation.ID,
Version: newVersion,
BaseCommissionMode: allocation.BaseCommissionMode,
BaseCommissionValue: allocation.BaseCommissionValue,
EnableTierCommission: allocation.EnableTierCommission,
EffectiveFrom: now,
AllocationID: allocation.ID,
Version: newVersion,
BaseCommissionMode: allocation.BaseCommissionMode,
BaseCommissionValue: allocation.BaseCommissionValue,
EffectiveFrom: now,
}
if err := s.configStore.Create(ctx, newConfig); err != nil {

View File

@@ -96,14 +96,12 @@ func (s *CommissionRecordStore) ListByShopID(ctx context.Context, opts *store.Qu
}
type CommissionStats struct {
TotalAmount int64
CostDiffAmount int64
OneTimeAmount int64
TierBonusAmount int64
TotalCount int64
CostDiffCount int64
OneTimeCount int64
TierBonusCount int64
TotalAmount int64
CostDiffAmount int64
OneTimeAmount int64
TotalCount int64
CostDiffCount int64
OneTimeCount int64
}
func (s *CommissionRecordStore) GetStats(ctx context.Context, filters *CommissionRecordListFilters) (*CommissionStats, error) {
@@ -128,11 +126,9 @@ func (s *CommissionRecordStore) GetStats(ctx context.Context, filters *Commissio
COALESCE(SUM(amount), 0) as total_amount,
COALESCE(SUM(CASE WHEN commission_source = 'cost_diff' THEN amount ELSE 0 END), 0) as cost_diff_amount,
COALESCE(SUM(CASE WHEN commission_source = 'one_time' THEN amount ELSE 0 END), 0) as one_time_amount,
COALESCE(SUM(CASE WHEN commission_source = 'tier_bonus' THEN amount ELSE 0 END), 0) as tier_bonus_amount,
COUNT(*) as total_count,
COALESCE(SUM(CASE WHEN commission_source = 'cost_diff' THEN 1 ELSE 0 END), 0) as cost_diff_count,
COALESCE(SUM(CASE WHEN commission_source = 'one_time' THEN 1 ELSE 0 END), 0) as one_time_count,
COALESCE(SUM(CASE WHEN commission_source = 'tier_bonus' THEN 1 ELSE 0 END), 0) as tier_bonus_count
COALESCE(SUM(CASE WHEN commission_source = 'one_time' THEN 1 ELSE 0 END), 0) as one_time_count
`).Scan(&stats)
if result.Error != nil {

View File

@@ -1,53 +0,0 @@
package postgres
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/model"
"gorm.io/gorm"
)
type ShopSeriesCommissionTierStore struct {
db *gorm.DB
}
func NewShopSeriesCommissionTierStore(db *gorm.DB) *ShopSeriesCommissionTierStore {
return &ShopSeriesCommissionTierStore{db: db}
}
func (s *ShopSeriesCommissionTierStore) Create(ctx context.Context, tier *model.ShopSeriesCommissionTier) error {
return s.db.WithContext(ctx).Create(tier).Error
}
func (s *ShopSeriesCommissionTierStore) GetByID(ctx context.Context, id uint) (*model.ShopSeriesCommissionTier, error) {
var tier model.ShopSeriesCommissionTier
if err := s.db.WithContext(ctx).First(&tier, id).Error; err != nil {
return nil, err
}
return &tier, nil
}
func (s *ShopSeriesCommissionTierStore) Update(ctx context.Context, tier *model.ShopSeriesCommissionTier) error {
return s.db.WithContext(ctx).Save(tier).Error
}
func (s *ShopSeriesCommissionTierStore) Delete(ctx context.Context, id uint) error {
return s.db.WithContext(ctx).Delete(&model.ShopSeriesCommissionTier{}, id).Error
}
func (s *ShopSeriesCommissionTierStore) ListByAllocationID(ctx context.Context, allocationID uint) ([]*model.ShopSeriesCommissionTier, error) {
var tiers []*model.ShopSeriesCommissionTier
if err := s.db.WithContext(ctx).
Where("allocation_id = ?", allocationID).
Order("threshold_value ASC").
Find(&tiers).Error; err != nil {
return nil, err
}
return tiers, nil
}
func (s *ShopSeriesCommissionTierStore) DeleteByAllocationID(ctx context.Context, allocationID uint) error {
return s.db.WithContext(ctx).
Where("allocation_id = ?", allocationID).
Delete(&model.ShopSeriesCommissionTier{}).Error
}

View File

@@ -53,22 +53,6 @@ func (h *CommissionStatsUpdateHandler) HandleCommissionStatsUpdate(ctx context.C
return asynq.SkipRetry
}
allocation, err := h.allocationStore.GetByID(ctx, payload.AllocationID)
if err != nil {
h.logger.Error("获取分配记录失败",
zap.Uint("allocation_id", payload.AllocationID),
zap.Error(err),
)
return asynq.SkipRetry
}
if !allocation.EnableTierCommission {
h.logger.Info("分配未启用梯度返佣,跳过统计更新",
zap.Uint("allocation_id", payload.AllocationID),
)
return nil
}
now := time.Now()
period := getCurrentPeriod(now)
redisKey := constants.RedisCommissionStatsKey(payload.AllocationID, period)