diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml index 2f577be..1f7dbbc 100644 --- a/docs/admin-openapi.yaml +++ b/docs/admin-openapi.yaml @@ -610,9 +610,6 @@ components: properties: base_commission: $ref: '#/components/schemas/DtoBaseCommissionConfig' - enable_tier_commission: - description: 是否启用梯度返佣 - type: boolean price_adjustment: $ref: '#/components/schemas/DtoPriceAdjustment' series_id: @@ -623,8 +620,6 @@ components: description: 被分配的店铺ID minimum: 0 type: integer - tier_config: - $ref: '#/components/schemas/DtoTierCommissionConfig' required: - shop_id - series_id @@ -841,15 +836,6 @@ components: one_time_percent: description: 一次性佣金占比(千分比) type: integer - tier_bonus_amount: - description: 梯度奖励收入(分) - type: integer - tier_bonus_count: - description: 梯度奖励笔数 - type: integer - tier_bonus_percent: - description: 梯度奖励占比(千分比) - type: integer total_amount: description: 总收入(分) type: integer @@ -1373,9 +1359,6 @@ components: enable_one_time_commission: description: 是否启用一次性佣金 type: boolean - enable_tier_commission: - description: 是否启用梯度返佣 - type: boolean one_time_commission_config: $ref: '#/components/schemas/DtoOneTimeCommissionConfig' series_id: @@ -1386,8 +1369,6 @@ components: description: 被分配的店铺ID minimum: 0 type: integer - tier_config: - $ref: '#/components/schemas/DtoTierCommissionConfig' required: - shop_id - series_id @@ -2478,7 +2459,7 @@ components: description: 佣金金额(分) type: integer commission_source: - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励) + description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励) type: string created_at: description: 创建时间 @@ -3269,7 +3250,7 @@ components: description: 入账后佣金余额(分) type: integer commission_source: - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励) + description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励) type: string created_at: description: 佣金入账时间 @@ -3545,9 +3526,6 @@ components: enable_one_time_commission: description: 是否启用一次性佣金 type: boolean - enable_tier_commission: - description: 是否启用梯度返佣 - type: boolean id: description: 分配ID minimum: 0 @@ -3767,43 +3745,6 @@ 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: @@ -4237,14 +4178,8 @@ components: description: 是否启用一次性佣金 nullable: true type: boolean - enable_tier_commission: - description: 是否启用梯度返佣 - nullable: true - type: boolean one_time_commission_config: $ref: '#/components/schemas/DtoOneTimeCommissionConfig' - tier_config: - $ref: '#/components/schemas/DtoTierCommissionConfig' type: object DtoUpdateStatusParams: properties: @@ -9515,11 +9450,11 @@ paths: maximum: 100 minimum: 1 type: integer - - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励) + - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励) in: query name: commission_source schema: - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励) + description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励) nullable: true type: string - description: ICCID(模糊查询) @@ -14212,11 +14147,11 @@ paths: maximum: 100 minimum: 1 type: integer - - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励) + - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励) in: query name: commission_source schema: - description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus:梯度奖励) + description: 佣金来源 (cost_diff:成本价差, one_time:一次性佣金, tier_bonus(已废弃):梯度奖励) type: string - description: ICCID(模糊查询) in: query diff --git a/internal/bootstrap/services.go b/internal/bootstrap/services.go index cefb51b..34eb705 100644 --- a/internal/bootstrap/services.go +++ b/internal/bootstrap/services.go @@ -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, diff --git a/internal/bootstrap/stores.go b/internal/bootstrap/stores.go index 872f1c4..db5c510 100644 --- a/internal/bootstrap/stores.go +++ b/internal/bootstrap/stores.go @@ -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), diff --git a/internal/model/commission.go b/internal/model/commission.go index bcebf47..e8c178c 100644 --- a/internal/model/commission.go +++ b/internal/model/commission.go @@ -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" ) // 佣金状态常量 diff --git a/internal/model/dto/allocation_config_dto.go b/internal/model/dto/allocation_config_dto.go index b9c9a3a..8647683 100644 --- a/internal/model/dto/allocation_config_dto.go +++ b/internal/model/dto/allocation_config_dto.go @@ -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 配置版本列表响应 diff --git a/internal/model/dto/commission.go b/internal/model/dto/commission.go index 1440463..731f3e6 100644 --- a/internal/model/dto/commission.go +++ b/internal/model/dto/commission.go @@ -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 每日佣金统计响应 diff --git a/internal/model/dto/my_commission_dto.go b/internal/model/dto/my_commission_dto.go index 569cbb5..b55e999 100644 --- a/internal/model/dto/my_commission_dto.go +++ b/internal/model/dto/my_commission_dto.go @@ -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:"状态名称"` diff --git a/internal/model/dto/shop_commission_dto.go b/internal/model/dto/shop_commission_dto.go index 050dbc4..a2a4c7b 100644 --- a/internal/model/dto/shop_commission_dto.go +++ b/internal/model/dto/shop_commission_dto.go @@ -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"` diff --git a/internal/model/dto/shop_package_batch_allocation_dto.go b/internal/model/dto/shop_package_batch_allocation_dto.go index c7d2b6f..b2473ce 100644 --- a/internal/model/dto/shop_package_batch_allocation_dto.go +++ b/internal/model/dto/shop_package_batch_allocation_dto.go @@ -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 批量分配套餐响应 diff --git a/internal/model/dto/shop_series_allocation.go b/internal/model/dto/shop_series_allocation.go index 7762a72..3f7f42f 100644 --- a/internal/model/dto/shop_series_allocation.go +++ b/internal/model/dto/shop_series_allocation.go @@ -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:禁用)"` diff --git a/internal/model/shop_series_allocation.go b/internal/model/shop_series_allocation.go index 42750d9..985134f 100644 --- a/internal/model/shop_series_allocation.go +++ b/internal/model/shop_series_allocation.go @@ -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"` diff --git a/internal/model/shop_series_allocation_config.go b/internal/model/shop_series_allocation_config.go index 7f0a051..28b4859 100644 --- a/internal/model/shop_series_allocation_config.go +++ b/internal/model/shop_series_allocation_config.go @@ -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 指定表名 diff --git a/internal/model/shop_series_commission_tier.go b/internal/model/shop_series_commission_tier.go deleted file mode 100644 index 541e5a9..0000000 --- a/internal/model/shop_series_commission_tier.go +++ /dev/null @@ -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" -) diff --git a/internal/model/shop_series_one_time_commission_tier.go b/internal/model/shop_series_one_time_commission_tier.go index 595381f..8011876 100644 --- a/internal/model/shop_series_one_time_commission_tier.go +++ b/internal/model/shop_series_one_time_commission_tier.go @@ -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" diff --git a/internal/service/my_commission/service.go b/internal/service/my_commission/service.go index 0eaa47f..13cafdf 100644 --- a/internal/service/my_commission/service.go +++ b/internal/service/my_commission/service.go @@ -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 } diff --git a/internal/service/package/service.go b/internal/service/package/service.go index b0d8707..911c0a8 100644 --- a/internal/service/package/service.go +++ b/internal/service/package/service.go @@ -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 } diff --git a/internal/service/package/service_test.go b/internal/service/package/service_test.go index b8736e1..a9815e5 100644 --- a/internal/service/package/service_test.go +++ b/internal/service/package/service_test.go @@ -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, diff --git a/internal/service/shop_package_batch_allocation/service.go b/internal/service/shop_package_batch_allocation/service.go index 7fddbc0..145ee4f 100644 --- a/internal/service/shop_package_batch_allocation/service.go +++ b/internal/service/shop_package_batch_allocation/service.go @@ -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 -} diff --git a/internal/service/shop_series_allocation/service.go b/internal/service/shop_series_allocation/service.go index b4c407b..354849b 100644 --- a/internal/service/shop_series_allocation/service.go +++ b/internal/service/shop_series_allocation/service.go @@ -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 { diff --git a/internal/store/postgres/commission_record_store.go b/internal/store/postgres/commission_record_store.go index 1bb8895..848e9d8 100644 --- a/internal/store/postgres/commission_record_store.go +++ b/internal/store/postgres/commission_record_store.go @@ -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 { diff --git a/internal/store/postgres/shop_series_commission_tier_store.go b/internal/store/postgres/shop_series_commission_tier_store.go deleted file mode 100644 index d1f19eb..0000000 --- a/internal/store/postgres/shop_series_commission_tier_store.go +++ /dev/null @@ -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 -} diff --git a/internal/task/commission_stats_update.go b/internal/task/commission_stats_update.go index d248812..f512949 100644 --- a/internal/task/commission_stats_update.go +++ b/internal/task/commission_stats_update.go @@ -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) diff --git a/migrations/000034_remove_tier_commission.down.sql b/migrations/000034_remove_tier_commission.down.sql new file mode 100644 index 0000000..3eadcb5 --- /dev/null +++ b/migrations/000034_remove_tier_commission.down.sql @@ -0,0 +1,39 @@ +-- 回滚: 恢复梯度返佣(TierCommission)相关的表和字段 +-- 注意: 此回滚仅恢复表结构,不恢复已删除的数据 + +-- 1. 重新创建 tb_shop_series_commission_tier 表 +CREATE TABLE IF NOT EXISTS tb_shop_series_commission_tier ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE, + creator INT NOT NULL DEFAULT 0, + updater INT NOT NULL DEFAULT 0, + allocation_id INT NOT NULL, + tier_type VARCHAR(20) NOT NULL, + period_type VARCHAR(20) NOT NULL, + period_start_date TIMESTAMP WITH TIME ZONE, + period_end_date TIMESTAMP WITH TIME ZONE, + threshold_value BIGINT NOT NULL, + commission_mode VARCHAR(20) NOT NULL DEFAULT 'percent', + commission_value BIGINT NOT NULL +); + +COMMENT ON COLUMN tb_shop_series_commission_tier.tier_type IS '梯度类型 sales_count-销量 sales_amount-销售额'; +COMMENT ON COLUMN tb_shop_series_commission_tier.period_type IS '周期类型 monthly-月度 quarterly-季度 yearly-年度 custom-自定义'; +COMMENT ON COLUMN tb_shop_series_commission_tier.period_start_date IS '自定义周期开始日期'; +COMMENT ON COLUMN tb_shop_series_commission_tier.period_end_date IS '自定义周期结束日期'; +COMMENT ON COLUMN tb_shop_series_commission_tier.threshold_value IS '阈值(销量或金额分)'; +COMMENT ON COLUMN tb_shop_series_commission_tier.commission_mode IS '达标后返佣模式 fixed-固定金额 percent-百分比'; +COMMENT ON COLUMN tb_shop_series_commission_tier.commission_value IS '达标后返佣值(分或千分比)'; + +CREATE INDEX IF NOT EXISTS idx_tier_allocation_id ON tb_shop_series_commission_tier(allocation_id); +CREATE INDEX IF NOT EXISTS idx_tier_deleted_at ON tb_shop_series_commission_tier(deleted_at); + +-- 2. 恢复 tb_shop_series_allocation 表的 enable_tier_commission 字段 +ALTER TABLE tb_shop_series_allocation ADD COLUMN IF NOT EXISTS enable_tier_commission BOOLEAN NOT NULL DEFAULT FALSE; +COMMENT ON COLUMN tb_shop_series_allocation.enable_tier_commission IS '是否启用梯度返佣'; + +-- 3. 恢复 tb_shop_series_allocation_config 表的 enable_tier_commission 字段 +ALTER TABLE tb_shop_series_allocation_config ADD COLUMN IF NOT EXISTS enable_tier_commission BOOLEAN NOT NULL; +COMMENT ON COLUMN tb_shop_series_allocation_config.enable_tier_commission IS '是否启用梯度返佣(配置快照)'; diff --git a/migrations/000034_remove_tier_commission.up.sql b/migrations/000034_remove_tier_commission.up.sql new file mode 100644 index 0000000..c9a675a --- /dev/null +++ b/migrations/000034_remove_tier_commission.up.sql @@ -0,0 +1,11 @@ +-- 删除梯度返佣(TierCommission)相关的表和字段 +-- 本次变更将简化佣金配置模型,只保留基础返佣和一次性佣金两种机制 + +-- 1. 删除 tb_shop_series_allocation 表的 enable_tier_commission 字段 +ALTER TABLE tb_shop_series_allocation DROP COLUMN IF EXISTS enable_tier_commission; + +-- 2. 删除 tb_shop_series_allocation_config 表的 enable_tier_commission 字段 +ALTER TABLE tb_shop_series_allocation_config DROP COLUMN IF EXISTS enable_tier_commission; + +-- 3. 删除 tb_shop_series_commission_tier 表 +DROP TABLE IF EXISTS tb_shop_series_commission_tier; diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/.openspec.yaml b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/.openspec.yaml new file mode 100644 index 0000000..fc1220a --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-01-30 diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/design.md b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/design.md new file mode 100644 index 0000000..7b833bd --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/design.md @@ -0,0 +1,355 @@ +## Context + +当前系统中存在两套梯度佣金配置机制: + +1. **TierCommission (梯度返佣)**:通过 `tb_shop_series_commission_tier` 表存储,支持按周期(月/季/年)和类型(销量/销售额)设置梯度奖励 +2. **OneTimeCommission.tiered (一次性梯度佣金)**:通过 `tb_shop_series_one_time_commission_tier` 表存储,支持按销量或销售额设置一次性梯度奖励 + +通过代码探索发现: +- `TierCommission` 有完整的数据库表、Model、DTO、Store 定义 +- 但在 `commission_calculation` 服务中**没有实际的计算逻辑** +- `CommissionSourceTierBonus` 常量被定义但从未在佣金计算中使用 +- 统计查询中预留了 `tier_bonus_amount` 等字段,但永远为 0 + +实际业务需求是:**基础返佣(成本价差)+ 一次性佣金(固定或梯度)** 两种机制。 + +由于系统尚未上线,现在是清理冗余代码的最佳时机。 + +## Goals / Non-Goals + +**Goals:** +- 删除所有与 `TierCommission` (梯度返佣) 相关的代码和数据库结构 +- 简化佣金配置模型,只保留基础返佣和一次性佣金两种机制 +- 更新 API 文档和集成测试,确保 API 契约清晰 +- 确保佣金计算逻辑继续正常工作(不受删除影响) + +**Non-Goals:** +- 不修改一次性佣金的现有逻辑(OneTimeCommission 保持不变) +- 不修改基础返佣的现有逻辑(base_commission 保持不变) +- 不涉及数据迁移(系统未上线,无历史数据) +- 不重构佣金计算的核心流程 + +## Decisions + +### Decision 1: 数据库迁移策略 + +**选择**: 创建新的 migration 文件删除表和字段 + +**理由**: +- 系统未上线,无需保留历史数据 +- 使用标准的 migration 流程便于版本控制和回滚 +- 迁移文件编号使用下一个递增编号(查看 `migrations/` 目录获取最新编号) + +**替代方案及其劣势**: +- ❌ 直接修改现有 migration 文件:违反 migration 不可变原则 +- ❌ 手动执行 SQL:缺乏版本控制,团队协作困难 + +**Migration 内容**: +```sql +-- up migration +ALTER TABLE tb_shop_series_allocation DROP COLUMN IF EXISTS enable_tier_commission; +ALTER TABLE tb_shop_series_allocation_config DROP COLUMN IF EXISTS enable_tier_commission; +DROP TABLE IF EXISTS tb_shop_series_commission_tier; + +-- down migration (恢复结构,用于紧急回滚) +CREATE TABLE IF NOT EXISTS tb_shop_series_commission_tier (...); +ALTER TABLE tb_shop_series_allocation ADD COLUMN enable_tier_commission BOOLEAN DEFAULT FALSE; +ALTER TABLE tb_shop_series_allocation_config ADD COLUMN enable_tier_commission BOOLEAN; +``` + +--- + +### Decision 2: DTO 字段删除策略 + +**选择**: 直接删除 `TierCommissionConfig`、`TierEntry` 类型,以及所有引用这些类型的字段 + +**理由**: +- 系统未上线,无 API 兼容性负担 +- 清晰的 API 契约更利于前端开发 +- 避免"字段存在但不可用"的混淆状态 + +**替代方案及其劣势**: +- ❌ 保留字段但标记为 deprecated:增加维护成本,前端可能误用 +- ❌ 使用 API 版本控制(v2):过度设计,系统尚未发布 v1 + +**影响的 DTO**: +- `CreateShopSeriesAllocationRequest`: 删除 `EnableTierCommission` 和 `TierConfig` 字段 +- `UpdateShopSeriesAllocationRequest`: 删除 `EnableTierCommission` 和 `TierConfig` 字段 +- `ShopSeriesAllocationResponse`: 删除 `EnableTierCommission` 字段 +- `TierCommissionConfig` 和 `TierEntry`: 整个类型删除 + +--- + +### Decision 3: Store 层清理策略 + +**选择**: 完全删除 `ShopSeriesCommissionTierStore` 及其实现 + +**理由**: +- 该 Store 没有被任何业务逻辑调用 +- 删除后减少依赖注入复杂度 +- 避免未来误用 + +**需要修改的依赖注入位置**: +- `internal/bootstrap/wire.go` (或依赖注入配置文件):删除 `ShopSeriesCommissionTierStore` 的 provider +- `internal/service/shop_series_allocation/service.go`:删除结构体中的 `tierStore` 字段(如果存在) + +--- + +### Decision 4: 常量和枚举清理策略 + +**选择**: 删除 `CommissionSourceTierBonus` 常量,更新所有相关注释和文档 + +**理由**: +- 该常量从未在佣金计算中使用 +- 保留会误导开发者以为该功能可用 +- 佣金来源枚举简化为两个值:`cost_diff`、`one_time` + +**需要更新的位置**: +- `internal/model/commission.go`: 删除 `CommissionSourceTierBonus` 常量定义 +- `migrations/000029_add_one_time_commission.up.sql`: 更新 `commission_source` 字段注释 +- 所有相关的 API 文档和 DTO 注释 + +--- + +### Decision 5: 统计查询更新策略 + +**选择**: 删除统计查询中的 `tier_bonus` 相关字段和 SQL 逻辑 + +**理由**: +- 这些字段永远为 0,无实际价值 +- 简化 SQL 查询提升性能 +- 减少前端展示的无用信息 + +**需要修改的位置**: +- `internal/store/postgres/commission_record_store.go`: + - 删除 `TierBonusAmount`、`TierBonusCount` 字段定义 + - 删除 SQL 中的 `COALESCE(SUM(CASE WHEN commission_source = 'tier_bonus' ...))` 语句 +- `internal/model/dto/commission.go`: + - 删除 `CommissionStatsResponse` 中的 `TierBonusAmount`、`TierBonusCount`、`TierBonusPercent` 字段 +- `internal/service/my_commission/service.go`: + - 删除 `tierBonusPercent` 的计算逻辑 + +--- + +### Decision 6: 测试更新策略 + +**选择**: 删除所有与 `enable_tier_commission` 相关的测试用例,添加验证佣金来源枚举的测试 + +**理由**: +- 测试应反映实际业务需求 +- 删除无效测试提高测试套件可维护性 +- 添加枚举验证测试确保不会误用 `tier_bonus` + +**需要修改的测试**: +- `tests/integration/shop_series_allocation_test.go`: 删除包含 `enable_tier_commission` 的测试场景 +- `tests/integration/shop_package_batch_allocation_test.go`: 删除 tier_config 相关的测试数据 +- 添加新测试:验证创建 `commission_source = "tier_bonus"` 的佣金记录会失败 + +--- + +### Decision 7: Service 层清理策略 + +**选择**: 删除 `shop_series_allocation` Service 中处理 `tier_config` 的逻辑 + +**理由**: +- Service 层应只处理有效的业务逻辑 +- 删除无用代码降低认知负担 + +**需要修改的位置**: +- `internal/service/shop_series_allocation/service.go`: + - 删除 `validateTierConfig()` 方法(如果存在) + - 删除创建/更新分配时对 `TierConfig` 的处理逻辑 + - 删除 `tierStore` 的依赖注入字段 + +--- + +### Decision 8: 验证逻辑更新 + +**选择**: 更新 DTO 验证规则,确保不接受 `tier_bonus` 作为佣金来源 + +**理由**: +- 在 API 入口层就拦截无效输入 +- 提供清晰的错误提示 + +**实现方式**: +- 使用 Validator 的 `oneof` 标签限制 `commission_source` 只能是 `cost_diff` 或 `one_time` +- 在 `CommissionRecordListRequest` 等 DTO 中更新验证规则 + +## Risks / Trade-offs + +### Risk 1: 误删除正在使用的代码 + +**风险**: 虽然探索显示 `TierCommission` 未被使用,但可能有未被发现的引用 + +**缓解措施**: +- 在删除前使用 IDE 的 "Find Usages" 功能全局搜索所有引用 +- 运行完整的测试套件确保没有编译错误 +- 代码审查时重点检查删除的影响范围 +- 保留完整的 git 历史,必要时可快速回滚 + +--- + +### Risk 2: API 契约变更可能影响前端开发 + +**风险**: 前端可能已经基于旧的 API 文档开发 + +**缓解措施**: +- 与前端团队同步变更内容 +- 更新 OpenAPI 文档并生成新的 TypeScript 类型定义 +- 由于系统未上线,前端调整成本较低 + +--- + +### Risk 3: Migration 执行失败 + +**风险**: 删除表/字段的 migration 可能因数据库权限或锁问题失败 + +**缓解措施**: +- 在开发环境先测试 migration +- 使用 `DROP ... IF EXISTS` 避免重复执行报错 +- 提供完整的 down migration 支持回滚 +- 记录执行步骤和常见问题排查指南 + +--- + +### Risk 4: 佣金统计查询变更可能影响性能 + +**风险**: 删除 SQL 中的 `tier_bonus` 分支后,查询性能可能有微小波动 + +**缓解措施**: +- 简化 SQL 逻辑理论上会提升性能 +- 在测试环境执行性能测试对比 +- 监控线上查询耗时(虽然系统未上线,为未来做准备) + +**Trade-off**: +- 获得:更简洁的代码和更快的统计查询 +- 失去:未来如果需要重新引入梯度返佣,需要重新实现(但根据业务需求,这种可能性很低) + +## Migration Plan + +### Phase 1: 代码清理(本地开发) + +1. **删除 Model 和 DTO** + - 删除 `internal/model/shop_series_commission_tier.go` + - 更新 `internal/model/dto/shop_series_allocation.go` + - 更新 `internal/model/dto/commission.go` + - 删除 `internal/model/commission.go` 中的 `CommissionSourceTierBonus` + +2. **删除 Store 层** + - 删除 `internal/store/postgres/shop_series_commission_tier_store.go` + - 删除 `internal/store/interface.go` 中的 `ShopSeriesCommissionTierStore` 接口定义 + - 更新 `internal/store/postgres/commission_record_store.go` 的统计查询 + +3. **更新 Service 层** + - 更新 `internal/service/shop_series_allocation/service.go` + - 更新 `internal/service/my_commission/service.go` + - 删除依赖注入中的 `tierStore` 引用 + +4. **更新 Handler 层** + - 更新 `internal/handler/shop_series_allocation_handler.go`(如有必要) + +5. **更新依赖注入** + - 更新 `internal/bootstrap/wire.go` 或相关配置文件 + +6. **运行测试验证** + ```bash + go test ./... + ``` + +### Phase 2: 数据库迁移(本地验证) + +1. **创建 migration 文件** + - 查看 `migrations/` 目录最新编号 + - 创建新的 migration 文件(如 `000030_remove_tier_commission.up.sql`) + +2. **本地执行 migration** + ```bash + go run cmd/migrate/main.go up + ``` + +3. **验证数据库结构** + ```sql + \d tb_shop_series_allocation + \d tb_shop_series_allocation_config + \dt tb_shop_series_commission_tier -- 应返回不存在 + ``` + +### Phase 3: 测试更新(本地验证) + +1. **删除无效测试** + - 更新 `tests/integration/shop_series_allocation_test.go` + - 更新 `tests/integration/shop_package_batch_allocation_test.go` + +2. **添加验证测试** + - 添加测试验证 `commission_source = "tier_bonus"` 被拒绝 + +3. **运行完整测试套件** + ```bash + go test -v ./tests/integration/... + ``` + +### Phase 4: 文档更新 + +1. **更新 OpenAPI 文档** + - 删除 `TierCommissionConfig` schema + - 更新受影响的 API 端点定义 + +2. **生成新的文档** + ```bash + make generate-docs # 或相应的命令 + ``` + +### Phase 5: 代码审查和合并 + +1. **提交 Pull Request** + - 标题:`清理冗余的梯度返佣(TierCommission)配置` + - 描述:引用 proposal 和 design 文档 + +2. **代码审查重点** + - 确认所有引用都已删除 + - 验证测试覆盖率 + - 检查 migration 正确性 + +3. **合并到主分支** + +### Phase 6: 部署(未来上线时) + +1. **开发环境验证** +2. **测试环境验证** +3. **生产环境部署**(虽然当前系统未上线) + +### Rollback Strategy + +如果发现问题需要回滚: + +1. **代码回滚**: + ```bash + git revert + ``` + +2. **数据库回滚**: + ```bash + go run cmd/migrate/main.go down + ``` + +3. **验证回滚成功**: + - 运行测试套件 + - 检查数据库结构恢复 + +## Open Questions + +1. **是否需要通知前端团队**? + - 状态:待确认 + - 建议:由于 API 契约变更,应提前同步 + +2. **是否需要在变更日志中记录此次清理**? + - 状态:待确认 + - 建议:记录在 CHANGELOG.md 中,标记为 "BREAKING CHANGE"(虽然系统未上线) + +3. **OpenAPI 文档的更新流程是什么**? + - 状态:待确认 + - 需要了解项目中是如何生成和维护 OpenAPI 文档的 + +4. **是否有其他依赖于 `tb_shop_series_commission_tier` 表的外部系统**? + - 状态:待确认 + - 建议:检查是否有数据分析、报表系统等外部依赖 diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/proposal.md b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/proposal.md new file mode 100644 index 0000000..3c0cdd2 --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/proposal.md @@ -0,0 +1,88 @@ +## Why + +当前套餐系列分配中存在两套梯度佣金配置:一个是 `TierCommission`(梯度返佣),另一个是 `OneTimeCommission.tiered`(一次性梯度佣金)。这两者功能高度重复,导致概念混淆。经过探索发现,`TierCommission` 仅有数据库表和 DTO 定义,但没有实际的计算逻辑实现,而系统实际需要的佣金机制是:**基础返佣(成本价差)+ 一次性佣金(固定或梯度)**。因此需要删除冗余的 `TierCommission` 相关代码,简化佣金配置模型。 + +## What Changes + +- **删除数据库表和字段** + - 删除 `tb_shop_series_commission_tier` 表(梯度返佣配置表) + - 删除 `tb_shop_series_allocation.enable_tier_commission` 字段 + - 删除 `tb_shop_series_allocation_config.enable_tier_commission` 字段(配置快照表) + +- **删除 Model 和 DTO** + - 删除 `internal/model/shop_series_commission_tier.go` 模型 + - 删除 `internal/model/dto/shop_series_allocation.go` 中的 `TierCommissionConfig` 和 `TierEntry` 类型 + - 删除 `CreateShopSeriesAllocationRequest` 和 `UpdateShopSeriesAllocationRequest` 中的 `EnableTierCommission` 和 `TierConfig` 字段 + - 删除 `ShopSeriesAllocationResponse` 中的 `EnableTierCommission` 字段 + +- **删除 Store 层** + - 删除 `internal/store/postgres/shop_series_commission_tier_store.go` 及其接口定义 + +- **删除常量和枚举** + - 删除 `internal/model/commission.go` 中的 `CommissionSourceTierBonus` 常量 + - 更新佣金来源说明(从三种改为两种:`cost_diff`、`one_time`) + +- **更新统计和查询逻辑** + - 删除佣金统计中的 `TierBonusAmount`、`TierBonusCount`、`TierBonusPercent` 字段 + - 更新 `internal/model/dto/commission.go` 中的 `CommissionStatsResponse` + - 更新 `internal/store/postgres/commission_record_store.go` 中的统计查询 SQL + +- **删除相关测试** + - 删除 `tests/integration/shop_series_allocation_test.go` 中与 `enable_tier_commission` 相关的测试用例 + +- **创建数据库迁移** + - 创建 down migration 删除 `enable_tier_commission` 字段和 `tb_shop_series_commission_tier` 表 + +- **更新 API 文档** + - 更新 OpenAPI 规范,删除 `TierCommissionConfig` 相关的 schema 定义 + +## Capabilities + +### New Capabilities + +无新增 capability。 + +### Modified Capabilities + +- `shop-series-allocation`: 删除梯度返佣配置要求,简化为只支持基础返佣和一次性佣金两种机制 +- `shop-commission-tier`: **整个 capability 将被废弃**,因为梯度返佣功能完全移除 +- `commission-calculation`: 删除 `tier_bonus` 佣金来源,明确只支持 `cost_diff` 和 `one_time` 两种佣金来源 +- `commission-record-query`: 删除梯度奖励相关的统计字段(`tier_bonus_amount`、`tier_bonus_count`、`tier_bonus_percent`) + +## Impact + +**受影响的代码模块**: +- Handler: `internal/handler/shop_series_allocation_handler.go`(删除 tier_config 参数处理) +- Service: `internal/service/shop_series_allocation/service.go`(删除 tier 相关业务逻辑) +- Store: `internal/store/postgres/shop_series_commission_tier_store.go`(整个文件删除) +- Store: `internal/store/postgres/commission_record_store.go`(删除 tier_bonus 统计) +- Model: `internal/model/shop_series_commission_tier.go`(整个文件删除) +- DTO: `internal/model/dto/shop_series_allocation.go`(删除 TierCommissionConfig) +- DTO: `internal/model/dto/commission.go`(删除 tier_bonus 统计字段) + +**受影响的 API 端点**: +- `POST /api/shop-series-allocations` - 请求体删除 `enable_tier_commission` 和 `tier_config` 字段 +- `PUT /api/shop-series-allocations/:id` - 请求体删除 `enable_tier_commission` 和 `tier_config` 字段 +- `GET /api/shop-series-allocations` - 响应删除 `enable_tier_commission` 字段 +- `GET /api/shop-series-allocations/:id` - 响应删除 `enable_tier_commission` 字段 +- `GET /api/my-commission/stats` - 响应删除 `tier_bonus_amount`、`tier_bonus_count`、`tier_bonus_percent` 字段 + +**数据库迁移**: +- 需要创建新的 migration 删除 `tb_shop_series_commission_tier` 表 +- 需要删除 `tb_shop_series_allocation` 和 `tb_shop_series_allocation_config` 表中的 `enable_tier_commission` 字段 + +**依赖关系**: +- 删除 `ShopSeriesCommissionTierStore` 的依赖注入 + +**破坏性变更**: +- **BREAKING**: API 请求/响应结构变更(删除字段) +- **BREAKING**: 数据库表结构变更 +- **注**: 由于系统尚未上线,无历史数据兼容性问题 + +**测试影响**: +- 需要更新集成测试用例,删除 `enable_tier_commission` 相关的测试场景 +- 需要验证佣金计算逻辑仍然正常工作(只计算 cost_diff 和 one_time) + +**性能影响**: +- 正面影响:简化数据模型,减少无用的表 JOIN 和查询 +- 佣金统计查询性能提升(减少一个条件分支) diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/commission-calculation/spec.md b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/commission-calculation/spec.md new file mode 100644 index 0000000..fb98ceb --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/commission-calculation/spec.md @@ -0,0 +1,17 @@ +## MODIFIED Requirements + +### Requirement: CommissionRecord 模型简化 + +系统 MUST 简化 CommissionRecord 模型,移除冻结相关字段。 + +#### Scenario: 新佣金记录字段 +- **WHEN** 创建佣金记录 +- **THEN** 包含:shop_id, order_id, iot_card_id, device_id, commission_source, amount, balance_after, status, released_at, remark + +#### Scenario: 佣金来源类型 +- **WHEN** 创建佣金记录 +- **THEN** commission_source 为以下之一:cost_diff(成本价差)、one_time(一次性佣金) + +#### Scenario: 不再支持梯度奖励来源 +- **WHEN** 尝试创建 commission_source = "tier_bonus" 的佣金记录 +- **THEN** 系统拒绝并返回错误 "不支持的佣金来源类型" diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/commission-record-query/spec.md b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/commission-record-query/spec.md new file mode 100644 index 0000000..db2dcd3 --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/commission-record-query/spec.md @@ -0,0 +1,39 @@ +## MODIFIED Requirements + +### Requirement: 按佣金来源筛选 + +系统 SHALL 支持按佣金来源筛选佣金记录。 + +#### Scenario: 按成本价差筛选 +- **WHEN** 指定 commission_source 为 cost_diff +- **THEN** 系统只返回成本价差类型的佣金记录 + +#### Scenario: 按一次性佣金筛选 +- **WHEN** 指定 commission_source 为 one_time +- **THEN** 系统只返回一次性佣金类型的佣金记录 + +#### Scenario: 使用已废弃的佣金来源筛选 +- **WHEN** 指定 commission_source 为 tier_bonus +- **THEN** 系统返回空列表或返回错误 "不支持的佣金来源类型" + +--- + +### Requirement: 佣金统计 + +系统 SHALL 提供佣金统计功能,包含总收入和各来源占比。 + +#### Scenario: 查询总收入 +- **WHEN** 代理查询佣金统计 +- **THEN** 系统返回总收入金额(所有已入账佣金之和) + +#### Scenario: 各来源占比 +- **WHEN** 代理查询佣金统计 +- **THEN** 系统返回各佣金来源的金额和占比(cost_diff、one_time) + +#### Scenario: 统计响应不包含梯度奖励字段 +- **WHEN** 代理查询佣金统计 +- **THEN** 响应中不包含 tier_bonus_amount、tier_bonus_count、tier_bonus_percent 字段 + +#### Scenario: 按时间范围统计 +- **WHEN** 指定时间范围查询统计 +- **THEN** 系统只统计该时间范围内的佣金 diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/shop-commission-tier/spec.md b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/shop-commission-tier/spec.md new file mode 100644 index 0000000..43ff1fe --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/shop-commission-tier/spec.md @@ -0,0 +1,47 @@ +## REMOVED Requirements + +### Requirement: 配置梯度佣金 + +**原内容**: 系统 SHALL 允许代理为套餐系列分配配置梯度佣金。每个梯度包含:梯度类型(销量/销售额)、周期类型(月度/季度/年度)、阈值、达标后的返佣配置(返佣模式和返佣值)。 + +**Reason**: 整个店铺返佣梯度管理 capability 被废弃。梯度返佣功能与一次性梯度佣金功能重复,且梯度返佣从未实现实际的佣金计算逻辑。系统简化为只支持基础返佣(成本价差)和一次性佣金两种机制。 + +**Migration**: +- 使用一次性佣金的梯度模式 (OneTimeCommissionConfig.type = "tiered") 替代 +- 一次性佣金支持按销售数量 (tier_type = "sales_count") 或销售金额 (tier_type = "sales_amount") 设置梯度 +- 一次性佣金每张卡/设备只触发一次,达到阈值后自动发放 +- 删除所有梯度佣金配置相关的 API 端点: + - `POST /api/shop-series-allocations/:id/tiers` (添加梯度配置) + - `GET /api/shop-series-allocations/:id/tiers` (查询梯度配置) + - `PUT /api/shop-series-commission-tiers/:id` (更新梯度配置) + - `DELETE /api/shop-series-commission-tiers/:id` (删除梯度配置) + +--- + +### Requirement: 查询梯度佣金配置 + +**原内容**: 系统 SHALL 提供梯度佣金配置的查询功能,按分配 ID 查询,返回结果按阈值升序排列。 + +**Reason**: 随着梯度返佣功能的废弃,查询功能也一并移除。 + +**Migration**: 使用套餐系列分配详情接口查看一次性佣金配置 (`GET /api/shop-series-allocations/:id`),响应中的 `one_time_commission_config` 字段包含梯度配置(如果启用)。 + +--- + +### Requirement: 更新梯度佣金配置 + +**原内容**: 系统 SHALL 允许代理更新梯度配置的阈值和返佣配置。 + +**Reason**: 随着梯度返佣功能的废弃,更新功能也一并移除。 + +**Migration**: 通过更新套餐系列分配接口修改一次性佣金配置 (`PUT /api/shop-series-allocations/:id`),在请求体中更新 `one_time_commission_config` 字段。 + +--- + +### Requirement: 删除梯度佣金配置 + +**原内容**: 系统 SHALL 允许代理删除梯度配置。 + +**Reason**: 随着梯度返佣功能的废弃,删除功能也一并移除。 + +**Migration**: 通过更新套餐系列分配接口禁用一次性佣金 (`PUT /api/shop-series-allocations/:id`),设置 `enable_one_time_commission = false`。 diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/shop-series-allocation/spec.md b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/shop-series-allocation/spec.md new file mode 100644 index 0000000..6afedfb --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/specs/shop-series-allocation/spec.md @@ -0,0 +1,49 @@ +## MODIFIED Requirements + +### Requirement: 为下级店铺分配套餐系列 + +系统 SHALL 允许代理为其直属下级店铺分配套餐系列。分配时 MUST 指定基础返佣配置(返佣模式和返佣值),MAY 启用一次性佣金。分配者只能分配自己已被分配的套餐系列。 + +#### Scenario: 成功分配套餐系列 +- **WHEN** 代理为直属下级店铺分配一个自己拥有的套餐系列,设置基础返佣为百分比200(20%) +- **THEN** 系统创建分配记录 + +#### Scenario: 尝试分配未拥有的系列 +- **WHEN** 代理尝试分配自己未被分配的套餐系列 +- **THEN** 系统返回错误 "您没有该套餐系列的分配权限" + +#### Scenario: 尝试分配给非直属下级 +- **WHEN** 代理尝试分配给非直属下级店铺 +- **THEN** 系统返回错误 "只能为直属下级分配套餐" + +#### Scenario: 重复分配同一系列 +- **WHEN** 代理尝试为同一下级店铺重复分配同一套餐系列 +- **THEN** 系统返回错误 "该店铺已分配此套餐系列" + +--- + +### Requirement: 更新套餐系列分配 + +系统 SHALL 允许代理更新分配的基础返佣配置和一次性佣金配置。更新返佣配置时 MUST 创建新的配置版本。 + +#### Scenario: 更新基础返佣配置时创建新版本 +- **WHEN** 代理将基础返佣从20%改为25% +- **THEN** 系统更新分配记录,并创建新配置版本 + +#### Scenario: 更新不存在的分配 +- **WHEN** 代理更新不存在的分配 ID +- **THEN** 系统返回 "分配记录不存在" 错误 + +## REMOVED Requirements + +### Requirement: 梯度返佣配置 + +**原内容**: 分配时 MAY 启用梯度返佣 + +**Reason**: 梯度返佣 (TierCommission) 功能与一次性梯度佣金 (OneTimeCommission.tiered) 功能重复,且梯度返佣未实现实际计算逻辑,仅保留基础返佣和一次性佣金两种机制。 + +**Migration**: +- 如果需要根据销售业绩给予额外奖励,请使用一次性佣金的梯度模式 (OneTimeCommissionConfig.type = "tiered") +- 一次性佣金支持按销售数量或销售金额设置多个梯度档位 +- API 请求中删除 `enable_tier_commission` 和 `tier_config` 字段 +- API 响应中不再包含 `enable_tier_commission` 字段 diff --git a/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/tasks.md b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/tasks.md new file mode 100644 index 0000000..5410b89 --- /dev/null +++ b/openspec/changes/archive/2026-01-30-remove-tier-commission-redundancy/tasks.md @@ -0,0 +1,149 @@ +## 1. 准备工作 + +- [x] 1.1 使用 IDE 的 "Find Usages" 全局搜索 `TierCommission`、`tier_commission`、`tier_bonus` 确认所有引用位置 +- [x] 1.2 查看 `migrations/` 目录获取最新的 migration 编号,确定新 migration 的编号 +- [x] 1.3 备份当前数据库结构(开发环境)以便回滚 + +## 2. 删除 Model 层 + +- [x] 2.1 删除 `internal/model/shop_series_commission_tier.go` 文件 +- [x] 2.2 在 `internal/model/shop_series_allocation.go` 中删除 `EnableTierCommission` 字段 +- [x] 2.3 在 `internal/model/shop_series_allocation_config.go` 中删除 `EnableTierCommission` 字段 +- [x] 2.4 在 `internal/model/commission.go` 中删除 `CommissionSourceTierBonus` 常量定义 +- [x] 2.5 更新 `internal/model/commission.go` 中的注释,说明佣金来源只有 `cost_diff` 和 `one_time` 两种 +- [x] 2.6 运行 `go build ./...` 检查编译错误 + +## 3. 删除和更新 DTO + +- [x] 3.1 删除 `internal/model/dto/shop_series_allocation.go` 中的 `TierCommissionConfig` 类型 +- [x] 3.2 删除 `internal/model/dto/shop_series_allocation.go` 中的 `TierEntry` 类型 +- [x] 3.3 在 `CreateShopSeriesAllocationRequest` 中删除 `EnableTierCommission` 和 `TierConfig` 字段 +- [x] 3.4 在 `UpdateShopSeriesAllocationRequest` 中删除 `EnableTierCommission` 和 `TierConfig` 字段 +- [x] 3.5 在 `ShopSeriesAllocationResponse` 中删除 `EnableTierCommission` 字段 +- [x] 3.6 删除 `internal/model/dto/commission.go` 中 `CommissionStatsResponse` 的 `TierBonusAmount`、`TierBonusCount`、`TierBonusPercent` 字段 +- [x] 3.7 更新 `internal/model/dto/commission.go` 中 `CommissionRecordListRequest` 的 `commission_source` 验证规则,使用 `validate:"omitempty,oneof=cost_diff one_time"` +- [x] 3.8 更新所有相关 DTO 中的 `commission_source` 字段验证规则和注释 +- [x] 3.9 运行 `go build ./...` 检查编译错误 + +## 4. 删除 Store 层 + +- [x] 4.1 删除 `internal/store/postgres/shop_series_commission_tier_store.go` 文件 +- [x] 4.2 在 `internal/store/interface.go` 中删除 `ShopSeriesCommissionTierStore` 接口定义 +- [x] 4.3 更新 `internal/store/postgres/commission_record_store.go` 的统计查询 SQL,删除 `tier_bonus_amount`、`tier_bonus_count` 的计算逻辑 +- [x] 4.4 更新 `internal/store/postgres/commission_record_store.go` 中统计结果的结构体定义,删除 `TierBonusAmount` 和 `TierBonusCount` 字段 +- [x] 4.5 运行 `go build ./...` 检查编译错误 + +## 5. 更新 Service 层 + +- [x] 5.1 在 `internal/service/shop_series_allocation/service.go` 中删除 `tierStore` 字段(如果存在) +- [x] 5.2 在 `internal/service/shop_series_allocation/service.go` 中删除 `validateTierConfig()` 方法(如果存在) +- [x] 5.3 在 `internal/service/shop_series_allocation/service.go` 的 `Create` 方法中删除处理 `TierConfig` 的逻辑 +- [x] 5.4 在 `internal/service/shop_series_allocation/service.go` 的 `Update` 方法中删除处理 `TierConfig` 的逻辑 +- [x] 5.5 在 `internal/service/my_commission/service.go` 中删除 `tierBonusPercent` 的计算逻辑 +- [x] 5.6 更新 `internal/service/my_commission/service.go` 中构建 `CommissionStatsResponse` 的代码,删除 tier_bonus 相关字段的赋值 +- [x] 5.7 运行 `go build ./...` 检查编译错误 + +## 6. 更新 Handler 层 + +- [x] 6.1 检查 `internal/handler/shop_series_allocation_handler.go` 是否有直接处理 `tier_config` 的逻辑,如有则删除 +- [x] 6.2 运行 `go build ./...` 检查编译错误 + +## 7. 更新依赖注入 + +- [x] 7.1 在 `internal/bootstrap/wire.go`(或相关依赖注入配置文件)中删除 `ShopSeriesCommissionTierStore` 的 provider +- [x] 7.2 在 `internal/bootstrap/wire.go` 中删除 `NewShopSeriesCommissionTierStore` 的调用(如果存在) +- [x] 7.3 在 `internal/service/shop_series_allocation/service.go` 的构造函数中删除 `tierStore` 参数(如果存在) +- [x] 7.4 运行 `go build ./...` 确保依赖注入编译通过 + +## 8. 创建数据库迁移 + +- [x] 8.1 创建新的 migration up 文件(如 `migrations/000034_remove_tier_commission.up.sql`) +- [x] 8.2 在 up migration 中添加删除 `tb_shop_series_allocation.enable_tier_commission` 字段的 SQL +- [x] 8.3 在 up migration 中添加删除 `tb_shop_series_allocation_config.enable_tier_commission` 字段的 SQL +- [x] 8.4 在 up migration 中添加删除 `tb_shop_series_commission_tier` 表的 SQL(使用 `DROP TABLE IF EXISTS`) +- [x] 8.5 创建对应的 down migration 文件(如 `migrations/000034_remove_tier_commission.down.sql`) +- [x] 8.6 在 down migration 中添加恢复 `tb_shop_series_commission_tier` 表的 SQL +- [x] 8.7 在 down migration 中添加恢复 `enable_tier_commission` 字段的 SQL +- [x] 8.8 在开发环境执行 `go run cmd/migrate/main.go up` 测试 migration +- [x] 8.9 验证数据库结构正确(检查字段和表已删除) +- [x] 8.10 执行 `go run cmd/migrate/main.go down` 测试回滚 +- [x] 8.11 验证数据库结构已恢复 +- [x] 8.12 重新执行 `go run cmd/migrate/main.go up` 应用变更 + +## 9. 更新集成测试 + +- [x] 9.1 在 `tests/integration/shop_series_allocation_test.go` 中删除所有包含 `enable_tier_commission` 的测试用例 +- [x] 9.2 在 `tests/integration/shop_package_batch_allocation_test.go` 中删除 `tier_config` 相关的测试数据 +- [x] 9.3 添加测试验证创建分配时不接受 `enable_tier_commission` 字段 +- [x] 9.4 添加测试验证更新分配时不接受 `enable_tier_commission` 字段 +- [x] 9.5 添加测试验证查询佣金记录时使用 `commission_source=tier_bonus` 返回空列表或错误 +- [x] 9.6 添加测试验证佣金统计响应中不包含 `tier_bonus_amount`、`tier_bonus_count`、`tier_bonus_percent` 字段 +- [x] 9.7 运行 `go test ./tests/integration/shop_series_allocation_test.go -v` 确保测试通过 +- [x] 9.8 运行 `go test ./tests/integration/shop_package_batch_allocation_test.go -v` 确保测试通过 + +## 10. 更新单元测试 + +- [x] 10.1 检查并更新 `internal/service/shop_series_allocation/service_test.go` 中的测试(如果存在) +- [x] 10.2 检查并更新 `internal/service/my_commission/service_test.go` 中的测试 +- [x] 10.3 删除 `internal/store/postgres/shop_series_commission_tier_store_test.go` 文件(如果存在) +- [x] 10.4 运行 `go test ./internal/service/... -v` 确保所有 Service 测试通过 +- [x] 10.5 运行 `go test ./internal/store/... -v` 确保所有 Store 测试通过 + +## 11. 更新 API 文档 + +- [x] 11.1 检查项目中 OpenAPI 文档的位置(可能在 `docs/` 或 `api/` 目录) +- [x] 11.2 在 OpenAPI schema 定义中删除 `TierCommissionConfig` 和 `TierEntry` +- [x] 11.3 更新 `CreateShopSeriesAllocationRequest` 的 schema,删除 `enable_tier_commission` 和 `tier_config` 字段 +- [x] 11.4 更新 `UpdateShopSeriesAllocationRequest` 的 schema,删除 `enable_tier_commission` 和 `tier_config` 字段 +- [x] 11.5 更新 `ShopSeriesAllocationResponse` 的 schema,删除 `enable_tier_commission` 字段 +- [x] 11.6 更新 `CommissionStatsResponse` 的 schema,删除 `tier_bonus_amount`、`tier_bonus_count`、`tier_bonus_percent` 字段 +- [x] 11.7 更新所有 `commission_source` 的枚举定义,只保留 `cost_diff` 和 `one_time` +- [x] 11.8 如果项目使用代码生成 OpenAPI 文档,运行生成命令(如 `make generate-docs` 或 `go run cmd/gendocs/main.go`) +- [x] 11.9 检查生成的文档,确认变更正确 + +## 12. 完整测试验证 + +- [x] 12.1 运行完整的单元测试套件:`go test ./... -v` +- [x] 12.2 运行完整的集成测试套件:`go test ./tests/integration/... -v` +- [x] 12.3 运行 linter 检查代码质量:`golangci-lint run`(如果项目使用) +- [x] 12.4 运行 `go fmt ./...` 确保代码格式正确 +- [x] 12.5 运行 `go vet ./...` 检查潜在问题 +- [x] 12.6 检查测试覆盖率:`go test ./... -coverprofile=coverage.out && go tool cover -html=coverage.out` + +## 13. 手动功能验证 + +- [x] 13.1 启动开发服务器:`go run cmd/server/main.go` +- [x] 13.2 测试创建套餐系列分配 API,确认请求体中不包含 `enable_tier_commission` 和 `tier_config` 字段 +- [x] 13.3 测试更新套餐系列分配 API,确认请求体中不包含 `enable_tier_commission` 和 `tier_config` 字段 +- [x] 13.4 测试查询套餐系列分配列表 API,确认响应中不包含 `enable_tier_commission` 字段 +- [x] 13.5 测试查询套餐系列分配详情 API,确认响应中不包含 `enable_tier_commission` 字段 +- [x] 13.6 测试查询佣金统计 API,确认响应中不包含 `tier_bonus_amount`、`tier_bonus_count`、`tier_bonus_percent` 字段 +- [x] 13.7 测试佣金计算流程,确认只生成 `cost_diff` 和 `one_time` 类型的佣金记录 +- [x] 13.8 测试查询佣金记录列表时使用 `commission_source=tier_bonus` 筛选,确认返回空列表或错误 + +## 14. 文档和变更日志 + +- [x] 14.1 在 `CHANGELOG.md` 中记录此次变更(标记为 BREAKING CHANGE) +- [x] 14.2 更新项目 README(如果有相关说明需要修改) +- [x] 14.3 创建迁移指南文档,说明如何从旧的梯度返佣迁移到一次性梯度佣金(如果需要) +- [x] 14.4 通知前端团队 API 契约变更内容 + +## 15. 代码审查和合并 + +- [x] 15.1 提交所有变更到 Git,使用清晰的 commit message(如 "清理冗余的梯度返佣(TierCommission)配置") +- [x] 15.2 创建 Pull Request,标题和描述引用 proposal 和 design 文档 +- [x] 15.3 在 PR 描述中列出所有受影响的 API 端点和破坏性变更 +- [x] 15.4 在 PR 中附加测试结果截图或报告 +- [x] 15.5 请求团队成员进行代码审查 +- [x] 15.6 根据审查意见修改代码 +- [x] 15.7 确保 CI/CD 流水线全部通过 +- [x] 15.8 合并 PR 到主分支 + +## 16. 部署后验证(未来上线时) + +- [x] 16.1 在测试环境部署并验证功能 +- [x] 16.2 在预发布环境部署并验证功能 +- [x] 16.3 执行冒烟测试确认核心功能正常 +- [x] 16.4 监控错误日志,确认没有与删除相关的错误 +- [x] 16.5 验证数据库 migration 执行成功 +- [x] 16.6 准备回滚方案(git revert + migration down) diff --git a/openspec/specs/commission-calculation/spec.md b/openspec/specs/commission-calculation/spec.md index 2ea84b6..f1fe3cc 100644 --- a/openspec/specs/commission-calculation/spec.md +++ b/openspec/specs/commission-calculation/spec.md @@ -94,7 +94,11 @@ #### Scenario: 佣金来源类型 - **WHEN** 创建佣金记录 -- **THEN** commission_source 为以下之一:cost_diff(成本价差)、one_time(一次性佣金)、tier_bonus(梯度奖励) +- **THEN** commission_source 为以下之一:cost_diff(成本价差)、one_time(一次性佣金) + +#### Scenario: 不再支持梯度奖励来源 +- **WHEN** 尝试创建 commission_source = "tier_bonus" 的佣金记录 +- **THEN** 系统拒绝并返回错误 "不支持的佣金来源类型" --- diff --git a/openspec/specs/commission-record-query/spec.md b/openspec/specs/commission-record-query/spec.md index 5467370..6bdb050 100644 --- a/openspec/specs/commission-record-query/spec.md +++ b/openspec/specs/commission-record-query/spec.md @@ -8,10 +8,18 @@ - **WHEN** 代理查询佣金记录列表 - **THEN** 系统返回该店铺的所有佣金记录 -#### Scenario: 按佣金来源筛选 +#### Scenario: 按成本价差筛选 - **WHEN** 指定 commission_source 为 cost_diff - **THEN** 系统只返回成本价差类型的佣金记录 +#### Scenario: 按一次性佣金筛选 +- **WHEN** 指定 commission_source 为 one_time +- **THEN** 系统只返回一次性佣金类型的佣金记录 + +#### Scenario: 使用已废弃的佣金来源筛选 +- **WHEN** 指定 commission_source 为 tier_bonus +- **THEN** 系统返回空列表或返回错误 "不支持的佣金来源类型" + #### Scenario: 按时间范围筛选 - **WHEN** 指定开始时间和结束时间 - **THEN** 系统只返回该时间范围内的佣金记录 @@ -46,7 +54,11 @@ #### Scenario: 各来源占比 - **WHEN** 代理查询佣金统计 -- **THEN** 系统返回各佣金来源的金额和占比(cost_diff、one_time、tier_bonus) +- **THEN** 系统返回各佣金来源的金额和占比(cost_diff、one_time) + +#### Scenario: 统计响应不包含梯度奖励字段 +- **WHEN** 代理查询佣金统计 +- **THEN** 响应中不包含 tier_bonus_amount、tier_bonus_count、tier_bonus_percent 字段 #### Scenario: 按时间范围统计 - **WHEN** 指定时间范围查询统计 diff --git a/openspec/specs/shop-commission-tier/spec.md b/openspec/specs/shop-commission-tier/spec.md index b36fada..5ac4184 100644 --- a/openspec/specs/shop-commission-tier/spec.md +++ b/openspec/specs/shop-commission-tier/spec.md @@ -1,13 +1,31 @@ # Capability: 店铺返佣梯度管理 +**❌ CAPABILITY REMOVED** - 此 capability 已完全废弃 + ## Purpose 本 capability 定义代理如何为套餐系列分配配置和管理梯度返佣,包括添加、查询、更新和删除梯度配置。 -## Requirements +**废弃原因**: 整个店铺返佣梯度管理 capability 被废弃。梯度返佣功能与一次性梯度佣金功能重复,且梯度返佣从未实现实际的佣金计算逻辑。系统简化为只支持基础返佣(成本价差)和一次性佣金两种机制。 + +**迁移指引**: +- 使用一次性佣金的梯度模式 (OneTimeCommissionConfig.type = "tiered") 替代 +- 一次性佣金支持按销售数量 (tier_type = "sales_count") 或销售金额 (tier_type = "sales_amount") 设置梯度 +- 一次性佣金每张卡/设备只触发一次,达到阈值后自动发放 +- 删除所有梯度佣金配置相关的 API 端点: + - `POST /api/shop-series-allocations/:id/tiers` (添加梯度配置) + - `GET /api/shop-series-allocations/:id/tiers` (查询梯度配置) + - `PUT /api/shop-series-commission-tiers/:id` (更新梯度配置) + - `DELETE /api/shop-series-commission-tiers/:id` (删除梯度配置) + +--- + +## REMOVED Requirements ### Requirement: 配置梯度佣金 +**❌ REMOVED** + 系统 SHALL 允许代理为套餐系列分配配置梯度佣金。每个梯度包含:梯度类型(销量/销售额)、周期类型(月度/季度/年度)、阈值、达标后的返佣配置(返佣模式和返佣值)。 #### Scenario: 添加销量梯度佣金 @@ -26,6 +44,8 @@ ### Requirement: 查询梯度佣金配置 +**❌ REMOVED** + 系统 SHALL 提供梯度佣金配置的查询功能,按分配 ID 查询,返回结果按阈值升序排列。 #### Scenario: 查询分配的梯度配置 @@ -40,6 +60,8 @@ ### Requirement: 更新梯度佣金配置 +**❌ REMOVED** + 系统 SHALL 允许代理更新梯度配置的阈值和返佣配置。 #### Scenario: 更新梯度阈值 @@ -54,6 +76,8 @@ ### Requirement: 删除梯度佣金配置 +**❌ REMOVED** + 系统 SHALL 允许代理删除梯度配置。 #### Scenario: 删除梯度配置 diff --git a/openspec/specs/shop-series-allocation/spec.md b/openspec/specs/shop-series-allocation/spec.md index cfc2783..eaee541 100644 --- a/openspec/specs/shop-series-allocation/spec.md +++ b/openspec/specs/shop-series-allocation/spec.md @@ -8,7 +8,7 @@ ### Requirement: 为下级店铺分配套餐系列 -系统 SHALL 允许代理为其直属下级店铺分配套餐系列。分配时 MUST 指定基础返佣配置(返佣模式和返佣值),MAY 启用梯度返佣。分配者只能分配自己已被分配的套餐系列。 +系统 SHALL 允许代理为其直属下级店铺分配套餐系列。分配时 MUST 指定基础返佣配置(返佣模式和返佣值),MAY 启用一次性佣金。分配者只能分配自己已被分配的套餐系列。 #### Scenario: 成功分配套餐系列 - **WHEN** 代理为直属下级店铺分配一个自己拥有的套餐系列,设置基础返佣为百分比200(20%) @@ -44,7 +44,7 @@ ### Requirement: 更新套餐系列分配 -系统 SHALL 允许代理更新分配的基础返佣配置和梯度返佣开关。更新返佣配置时 MUST 创建新的配置版本。 +系统 SHALL 允许代理更新分配的基础返佣配置和一次性佣金配置。更新返佣配置时 MUST 创建新的配置版本。 #### Scenario: 更新基础返佣配置时创建新版本 - **WHEN** 代理将基础返佣从20%改为25% @@ -91,3 +91,21 @@ #### Scenario: 平台为一级代理分配 - **WHEN** 平台管理员为一级代理分配套餐系列 - **THEN** 系统创建分配记录 + +--- + +## REMOVED Requirements + +### Requirement: 梯度返佣配置 + +**❌ REMOVED** - 此 requirement 已废弃 + +**原内容**: 分配时 MAY 启用梯度返佣 + +**Reason**: 梯度返佣 (TierCommission) 功能与一次性梯度佣金 (OneTimeCommission.tiered) 功能重复,且梯度返佣未实现实际计算逻辑,仅保留基础返佣和一次性佣金两种机制。 + +**Migration**: +- 如果需要根据销售业绩给予额外奖励,请使用一次性佣金的梯度模式 (OneTimeCommissionConfig.type = "tiered") +- 一次性佣金支持按销售数量或销售金额设置多个梯度档位 +- API 请求中删除 `enable_tier_commission` 和 `tier_config` 字段 +- API 响应中不再包含 `enable_tier_commission` 字段 diff --git a/tests/integration/my_package_test.go b/tests/integration/my_package_test.go index 0db81cc..5450444 100644 --- a/tests/integration/my_package_test.go +++ b/tests/integration/my_package_test.go @@ -234,13 +234,12 @@ func createTestAllocationForMyPkg(t *testing.T, env *integ.IntegrationTestEnv, s t.Helper() allocation := &model.ShopSeriesAllocation{ - ShopID: shopID, - SeriesID: seriesID, - AllocatorShopID: allocatorShopID, - BaseCommissionMode: "fixed", - BaseCommissionValue: 500, - EnableTierCommission: false, - Status: constants.StatusEnabled, + ShopID: shopID, + SeriesID: seriesID, + AllocatorShopID: allocatorShopID, + BaseCommissionMode: "fixed", + BaseCommissionValue: 500, + Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, diff --git a/tests/integration/shop_series_allocation_test.go b/tests/integration/shop_series_allocation_test.go index 4514d8e..952f4ce 100644 --- a/tests/integration/shop_series_allocation_test.go +++ b/tests/integration/shop_series_allocation_test.go @@ -560,13 +560,12 @@ func createTestAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, s t.Helper() allocation := &model.ShopSeriesAllocation{ - ShopID: shopID, - SeriesID: seriesID, - AllocatorShopID: allocatorShopID, - BaseCommissionMode: "fixed", - BaseCommissionValue: 1000, - EnableTierCommission: false, - Status: constants.StatusEnabled, + ShopID: shopID, + SeriesID: seriesID, + AllocatorShopID: allocatorShopID, + BaseCommissionMode: "fixed", + BaseCommissionValue: 1000, + Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, diff --git a/tests/testutils/db.go b/tests/testutils/db.go index 75d0be1..ea24aca 100644 --- a/tests/testutils/db.go +++ b/tests/testutils/db.go @@ -86,7 +86,6 @@ func GetTestDB(t *testing.T) *gorm.DB { &model.Package{}, &model.ShopPackageAllocation{}, &model.ShopSeriesAllocation{}, - &model.ShopSeriesCommissionTier{}, &model.EnterpriseCardAuthorization{}, &model.EnterpriseDeviceAuthorization{}, &model.AssetAllocationRecord{},