refactor: 一次性佣金配置从套餐级别提升到系列级别
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s

主要变更:
- 新增 tb_shop_series_allocation 表,存储系列级别的一次性佣金配置
- ShopPackageAllocation 移除 one_time_commission_amount 字段
- PackageSeries 新增 enable_one_time_commission 字段控制是否启用一次性佣金
- 新增 /api/admin/shop-series-allocations CRUD 接口
- 佣金计算逻辑改为从 ShopSeriesAllocation 获取一次性佣金金额
- 删除废弃的 ShopSeriesOneTimeCommissionTier 模型
- OpenAPI Tag '系列分配' 和 '单套餐分配' 合并为 '套餐分配'

迁移脚本:
- 000042: 重构佣金套餐模型
- 000043: 简化佣金分配
- 000044: 一次性佣金分配重构
- 000045: PackageSeries 添加 enable_one_time_commission 字段

测试:
- 新增验收测试 (shop_series_allocation, commission_calculation)
- 新增流程测试 (one_time_commission_chain)
- 删除过时的单元测试(已被验收测试覆盖)
This commit is contained in:
2026-02-04 14:28:44 +08:00
parent fba8e9e76b
commit b18ecfeb55
106 changed files with 9899 additions and 6608 deletions

View File

@@ -10,34 +10,29 @@ import (
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
pkggorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"gorm.io/gorm"
)
type Service struct {
allocationStore *postgres.ShopSeriesAllocationStore
configStore *postgres.ShopSeriesAllocationConfigStore
oneTimeCommissionTierStore *postgres.ShopSeriesOneTimeCommissionTierStore
shopStore *postgres.ShopStore
packageSeriesStore *postgres.PackageSeriesStore
packageStore *postgres.PackageStore
seriesAllocationStore *postgres.ShopSeriesAllocationStore
packageAllocationStore *postgres.ShopPackageAllocationStore
shopStore *postgres.ShopStore
packageSeriesStore *postgres.PackageSeriesStore
}
func New(
allocationStore *postgres.ShopSeriesAllocationStore,
configStore *postgres.ShopSeriesAllocationConfigStore,
oneTimeCommissionTierStore *postgres.ShopSeriesOneTimeCommissionTierStore,
seriesAllocationStore *postgres.ShopSeriesAllocationStore,
packageAllocationStore *postgres.ShopPackageAllocationStore,
shopStore *postgres.ShopStore,
packageSeriesStore *postgres.PackageSeriesStore,
packageStore *postgres.PackageStore,
) *Service {
return &Service{
allocationStore: allocationStore,
configStore: configStore,
oneTimeCommissionTierStore: oneTimeCommissionTierStore,
shopStore: shopStore,
packageSeriesStore: packageSeriesStore,
packageStore: packageStore,
seriesAllocationStore: seriesAllocationStore,
packageAllocationStore: packageAllocationStore,
shopStore: shopStore,
packageSeriesStore: packageSeriesStore,
}
}
@@ -62,16 +57,9 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesAllocatio
return nil, errors.Wrap(errors.CodeInternalError, err, "获取店铺失败")
}
isPlatformUser := userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform
isFirstLevelShop := targetShop.ParentID == nil
if isPlatformUser {
if !isFirstLevelShop {
return nil, errors.New(errors.CodeForbidden, "平台只能为一级店铺分配套餐")
}
} else {
if isFirstLevelShop || *targetShop.ParentID != allocatorShopID {
return nil, errors.New(errors.CodeForbidden, "只能为直属下级分配套餐")
if userType == constants.UserTypeAgent {
if targetShop.ParentID == nil || *targetShop.ParentID != allocatorShopID {
return nil, errors.New(errors.CodeForbidden, "只能为直属下级分配套餐系列")
}
}
@@ -83,49 +71,54 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesAllocatio
return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐系列失败")
}
if userType == constants.UserTypeAgent {
myAllocation, err := s.allocationStore.GetByShopAndSeries(ctx, allocatorShopID, req.SeriesID)
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(errors.CodeInternalError, err, "检查分配权限失败")
}
if myAllocation == nil || myAllocation.Status != constants.StatusEnabled {
return nil, errors.New(errors.CodeForbidden, "您没有该套餐系列的分配权限")
}
// 检查是否已存在分配(跳过数据权限过滤,避免误判)
skipCtx := pkggorm.SkipDataPermission(ctx)
exists, err := s.seriesAllocationStore.ExistsByShopAndSeries(skipCtx, req.ShopID, req.SeriesID)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "检查分配记录失败")
}
existing, _ := s.allocationStore.GetByShopAndSeries(ctx, req.ShopID, req.SeriesID)
if existing != nil {
if exists {
return nil, errors.New(errors.CodeConflict, "该店铺已分配此套餐系列")
}
if err := s.validateOneTimeCommissionConfig(req); err != nil {
return nil, err
}
allocation := &model.ShopSeriesAllocation{
ShopID: req.ShopID,
SeriesID: req.SeriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: req.BaseCommission.Mode,
BaseCommissionValue: req.BaseCommission.Value,
Status: constants.StatusEnabled,
}
// 处理一次性佣金配置
allocation.EnableOneTimeCommission = req.EnableOneTimeCommission
if req.EnableOneTimeCommission && req.OneTimeCommissionConfig != nil {
cfg := req.OneTimeCommissionConfig
allocation.OneTimeCommissionType = cfg.Type
allocation.OneTimeCommissionTrigger = cfg.Trigger
allocation.OneTimeCommissionThreshold = cfg.Threshold
// fixed 类型需要保存 mode 和 value
if cfg.Type == model.OneTimeCommissionTypeFixed {
allocation.OneTimeCommissionMode = cfg.Mode
allocation.OneTimeCommissionValue = cfg.Value
// 代理用户:检查自己是否有该系列的分配权限,且金额不能超过上级给的上限
// 平台用户:无上限限制,可自由设定金额
if userType == constants.UserTypeAgent {
allocatorAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(skipCtx, allocatorShopID, req.SeriesID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeForbidden, "您没有该套餐系列的分配权限")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取分配权限失败")
}
if req.OneTimeCommissionAmount > allocatorAllocation.OneTimeCommissionAmount {
return nil, errors.New(errors.CodeInvalidParam, "一次性佣金金额不能超过您的分配上限")
}
}
// 处理强充配置
allocation := &model.ShopSeriesAllocation{
ShopID: req.ShopID,
SeriesID: req.SeriesID,
AllocatorShopID: allocatorShopID,
OneTimeCommissionAmount: req.OneTimeCommissionAmount,
EnableOneTimeCommission: false,
OneTimeCommissionTrigger: "",
OneTimeCommissionThreshold: 0,
EnableForceRecharge: false,
ForceRechargeAmount: 0,
ForceRechargeTriggerType: 2,
Status: constants.StatusEnabled,
}
if req.EnableOneTimeCommission != nil {
allocation.EnableOneTimeCommission = *req.EnableOneTimeCommission
}
if req.OneTimeCommissionTrigger != "" {
allocation.OneTimeCommissionTrigger = req.OneTimeCommissionTrigger
}
if req.OneTimeCommissionThreshold != nil {
allocation.OneTimeCommissionThreshold = *req.OneTimeCommissionThreshold
}
if req.EnableForceRecharge != nil {
allocation.EnableForceRecharge = *req.EnableForceRecharge
}
@@ -138,23 +131,15 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesAllocatio
allocation.Creator = currentUserID
if err := s.allocationStore.Create(ctx, allocation); err != nil {
if err := s.seriesAllocationStore.Create(ctx, allocation); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "创建分配失败")
}
// 如果是梯度类型,保存梯度配置
if req.EnableOneTimeCommission && req.OneTimeCommissionConfig != nil &&
req.OneTimeCommissionConfig.Type == model.OneTimeCommissionTypeTiered {
if err := s.saveOneTimeCommissionTiers(ctx, allocation.ID, req.OneTimeCommissionConfig.Tiers, currentUserID); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "创建一次性佣金梯度配置失败")
}
}
return s.buildResponse(ctx, allocation, targetShop.ShopName, series.SeriesName)
return s.buildResponse(ctx, allocation, targetShop.ShopName, series.SeriesName, series.SeriesCode)
}
func (s *Service) Get(ctx context.Context, id uint) (*dto.ShopSeriesAllocationResponse, error) {
allocation, err := s.allocationStore.GetByID(ctx, id)
allocation, err := s.seriesAllocationStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "分配记录不存在")
@@ -167,14 +152,16 @@ func (s *Service) Get(ctx context.Context, id uint) (*dto.ShopSeriesAllocationRe
shopName := ""
seriesName := ""
seriesCode := ""
if shop != nil {
shopName = shop.ShopName
}
if series != nil {
seriesName = series.SeriesName
seriesCode = series.SeriesCode
}
return s.buildResponse(ctx, allocation, shopName, seriesName)
return s.buildResponse(ctx, allocation, shopName, seriesName, seriesCode)
}
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateShopSeriesAllocationRequest) (*dto.ShopSeriesAllocationResponse, error) {
@@ -183,7 +170,10 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateShopSeries
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
allocation, err := s.allocationStore.GetByID(ctx, id)
userType := middleware.GetUserTypeFromContext(ctx)
allocatorShopID := middleware.GetShopIDFromContext(ctx)
allocation, err := s.seriesAllocationStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "分配记录不存在")
@@ -191,52 +181,27 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateShopSeries
return nil, errors.Wrap(errors.CodeInternalError, err, "获取分配记录失败")
}
configChanged := false
if req.BaseCommission != nil {
if allocation.BaseCommissionMode != req.BaseCommission.Mode ||
allocation.BaseCommissionValue != req.BaseCommission.Value {
configChanged = true
if req.OneTimeCommissionAmount != nil {
newAmount := *req.OneTimeCommissionAmount
if userType == constants.UserTypeAgent {
allocatorAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, allocatorShopID, allocation.SeriesID)
if err == nil && allocatorAllocation != nil {
if newAmount > allocatorAllocation.OneTimeCommissionAmount {
return nil, errors.New(errors.CodeInvalidParam, "一次性佣金金额不能超过您的分配上限")
}
}
}
allocation.BaseCommissionMode = req.BaseCommission.Mode
allocation.BaseCommissionValue = req.BaseCommission.Value
allocation.OneTimeCommissionAmount = newAmount
}
enableOneTimeCommission := allocation.EnableOneTimeCommission
if req.EnableOneTimeCommission != nil {
enableOneTimeCommission = *req.EnableOneTimeCommission
}
if err := s.validateOneTimeCommissionConfigForUpdate(enableOneTimeCommission, req.OneTimeCommissionConfig); err != nil {
return nil, err
}
oneTimeCommissionChanged := false
if req.EnableOneTimeCommission != nil {
if allocation.EnableOneTimeCommission != *req.EnableOneTimeCommission {
oneTimeCommissionChanged = true
}
allocation.EnableOneTimeCommission = *req.EnableOneTimeCommission
}
if req.OneTimeCommissionConfig != nil && allocation.EnableOneTimeCommission {
cfg := req.OneTimeCommissionConfig
if allocation.OneTimeCommissionType != cfg.Type ||
allocation.OneTimeCommissionTrigger != cfg.Trigger ||
allocation.OneTimeCommissionThreshold != cfg.Threshold ||
allocation.OneTimeCommissionMode != cfg.Mode ||
allocation.OneTimeCommissionValue != cfg.Value {
oneTimeCommissionChanged = true
}
allocation.OneTimeCommissionType = cfg.Type
allocation.OneTimeCommissionTrigger = cfg.Trigger
allocation.OneTimeCommissionThreshold = cfg.Threshold
if cfg.Type == model.OneTimeCommissionTypeFixed {
allocation.OneTimeCommissionMode = cfg.Mode
allocation.OneTimeCommissionValue = cfg.Value
} else {
allocation.OneTimeCommissionMode = ""
allocation.OneTimeCommissionValue = 0
}
if req.OneTimeCommissionTrigger != nil {
allocation.OneTimeCommissionTrigger = *req.OneTimeCommissionTrigger
}
if req.OneTimeCommissionThreshold != nil {
allocation.OneTimeCommissionThreshold = *req.OneTimeCommissionThreshold
}
if req.EnableForceRecharge != nil {
allocation.EnableForceRecharge = *req.EnableForceRecharge
}
@@ -246,46 +211,36 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateShopSeries
if req.ForceRechargeTriggerType != nil {
allocation.ForceRechargeTriggerType = *req.ForceRechargeTriggerType
}
if req.Status != nil {
allocation.Status = *req.Status
}
allocation.Updater = currentUserID
if configChanged {
if err := s.createNewConfigVersion(ctx, allocation); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "创建配置版本失败")
}
}
if err := s.allocationStore.Update(ctx, allocation); err != nil {
if err := s.seriesAllocationStore.Update(ctx, allocation); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "更新分配失败")
}
if oneTimeCommissionChanged && req.OneTimeCommissionConfig != nil &&
req.OneTimeCommissionConfig.Type == model.OneTimeCommissionTypeTiered {
if err := s.oneTimeCommissionTierStore.DeleteByAllocationID(ctx, allocation.ID); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "清理旧梯度配置失败")
}
if err := s.saveOneTimeCommissionTiers(ctx, allocation.ID, req.OneTimeCommissionConfig.Tiers, currentUserID); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "更新一次性佣金梯度配置失败")
}
}
shop, _ := s.shopStore.GetByID(ctx, allocation.ShopID)
series, _ := s.packageSeriesStore.GetByID(ctx, allocation.SeriesID)
shopName := ""
seriesName := ""
seriesCode := ""
if shop != nil {
shopName = shop.ShopName
}
if series != nil {
seriesName = series.SeriesName
seriesCode = series.SeriesCode
}
return s.buildResponse(ctx, allocation, shopName, seriesName)
return s.buildResponse(ctx, allocation, shopName, seriesName, seriesCode)
}
func (s *Service) Delete(ctx context.Context, id uint) error {
allocation, err := s.allocationStore.GetByID(ctx, id)
skipCtx := pkggorm.SkipDataPermission(ctx)
_, err := s.seriesAllocationStore.GetByID(skipCtx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "分配记录不存在")
@@ -293,15 +248,15 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
return errors.Wrap(errors.CodeInternalError, err, "获取分配记录失败")
}
hasDependent, err := s.allocationStore.HasDependentAllocations(ctx, allocation.ShopID, allocation.SeriesID)
count, err := s.packageAllocationStore.CountBySeriesAllocationID(skipCtx, id)
if err != nil {
return errors.Wrap(errors.CodeInternalError, err, "检查依赖关系失败")
return errors.Wrap(errors.CodeInternalError, err, "检查关联套餐分配失败")
}
if hasDependent {
return errors.New(errors.CodeConflict, "存在下级依赖,无法删除")
if count > 0 {
return errors.New(errors.CodeInvalidParam, "存在关联的套餐分配,无法删除")
}
if err := s.allocationStore.Delete(ctx, id); err != nil {
if err := s.seriesAllocationStore.Delete(skipCtx, id); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "删除分配失败")
}
@@ -309,9 +264,6 @@ func (s *Service) Delete(ctx context.Context, id uint) error {
}
func (s *Service) List(ctx context.Context, req *dto.ShopSeriesAllocationListRequest) ([]*dto.ShopSeriesAllocationResponse, int64, error) {
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
opts := &store.QueryOptions{
Page: req.Page,
PageSize: req.PageSize,
@@ -331,14 +283,14 @@ func (s *Service) List(ctx context.Context, req *dto.ShopSeriesAllocationListReq
if req.SeriesID != nil {
filters["series_id"] = *req.SeriesID
}
if req.AllocatorShopID != nil {
filters["allocator_shop_id"] = *req.AllocatorShopID
}
if req.Status != nil {
filters["status"] = *req.Status
}
if shopID > 0 && userType == constants.UserTypeAgent {
filters["allocator_shop_id"] = shopID
}
allocations, total, err := s.allocationStore.List(ctx, opts, filters)
allocations, total, err := s.seriesAllocationStore.List(ctx, opts, filters)
if err != nil {
return nil, 0, errors.Wrap(errors.CodeInternalError, err, "查询分配列表失败")
}
@@ -350,233 +302,55 @@ func (s *Service) List(ctx context.Context, req *dto.ShopSeriesAllocationListReq
shopName := ""
seriesName := ""
seriesCode := ""
if shop != nil {
shopName = shop.ShopName
}
if series != nil {
seriesName = series.SeriesName
seriesCode = series.SeriesCode
}
resp, _ := s.buildResponse(ctx, a, shopName, seriesName)
resp, _ := s.buildResponse(ctx, a, shopName, seriesName, seriesCode)
responses[i] = resp
}
return responses, total, nil
}
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
_, err := s.allocationStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "分配记录不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取分配记录失败")
}
if err := s.allocationStore.UpdateStatus(ctx, id, status, currentUserID); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "更新状态失败")
}
return nil
}
func (s *Service) GetParentCostPrice(ctx context.Context, shopID, packageID uint) (int64, error) {
pkg, err := s.packageStore.GetByID(ctx, packageID)
if err != nil {
return 0, errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
shop, err := s.shopStore.GetByID(ctx, shopID)
if err != nil {
return 0, errors.Wrap(errors.CodeInternalError, err, "获取店铺失败")
}
if shop.ParentID == nil || *shop.ParentID == 0 {
return pkg.SuggestedCostPrice, nil
}
return 0, errors.New(errors.CodeInvalidParam, "自动计算成本价功能已移除,请手动设置成本价")
}
func (s *Service) buildResponse(ctx context.Context, a *model.ShopSeriesAllocation, shopName, seriesName string) (*dto.ShopSeriesAllocationResponse, error) {
allocatorShop, _ := s.shopStore.GetByID(ctx, a.AllocatorShopID)
func (s *Service) buildResponse(ctx context.Context, a *model.ShopSeriesAllocation, shopName, seriesName, seriesCode string) (*dto.ShopSeriesAllocationResponse, error) {
allocatorShopName := ""
if allocatorShop != nil {
allocatorShopName = allocatorShop.ShopName
}
resp := &dto.ShopSeriesAllocationResponse{
ID: a.ID,
ShopID: a.ShopID,
ShopName: shopName,
SeriesID: a.SeriesID,
SeriesName: seriesName,
AllocatorShopID: a.AllocatorShopID,
AllocatorShopName: allocatorShopName,
BaseCommission: dto.BaseCommissionConfig{
Mode: a.BaseCommissionMode,
Value: a.BaseCommissionValue,
},
EnableOneTimeCommission: a.EnableOneTimeCommission,
EnableForceRecharge: a.EnableForceRecharge,
ForceRechargeAmount: a.ForceRechargeAmount,
ForceRechargeTriggerType: a.ForceRechargeTriggerType,
Status: a.Status,
CreatedAt: a.CreatedAt.Format(time.RFC3339),
UpdatedAt: a.UpdatedAt.Format(time.RFC3339),
}
if a.EnableOneTimeCommission {
cfg := &dto.OneTimeCommissionConfig{
Type: a.OneTimeCommissionType,
Trigger: a.OneTimeCommissionTrigger,
Threshold: a.OneTimeCommissionThreshold,
Mode: a.OneTimeCommissionMode,
Value: a.OneTimeCommissionValue,
if a.AllocatorShopID > 0 {
allocatorShop, _ := s.shopStore.GetByID(ctx, a.AllocatorShopID)
if allocatorShop != nil {
allocatorShopName = allocatorShop.ShopName
}
if a.OneTimeCommissionType == model.OneTimeCommissionTypeTiered {
tiers, err := s.oneTimeCommissionTierStore.ListByAllocationID(ctx, a.ID)
if err == nil && len(tiers) > 0 {
cfg.Tiers = make([]dto.OneTimeCommissionTierEntry, len(tiers))
for i, t := range tiers {
cfg.Tiers[i] = dto.OneTimeCommissionTierEntry{
TierType: t.TierType,
Threshold: t.ThresholdValue,
Mode: t.CommissionMode,
Value: t.CommissionValue,
}
}
}
}
resp.OneTimeCommissionConfig = cfg
} else {
allocatorShopName = "平台"
}
return resp, nil
return &dto.ShopSeriesAllocationResponse{
ID: a.ID,
ShopID: a.ShopID,
ShopName: shopName,
SeriesID: a.SeriesID,
SeriesName: seriesName,
SeriesCode: seriesCode,
AllocatorShopID: a.AllocatorShopID,
AllocatorShopName: allocatorShopName,
OneTimeCommissionAmount: a.OneTimeCommissionAmount,
EnableOneTimeCommission: a.EnableOneTimeCommission,
OneTimeCommissionTrigger: a.OneTimeCommissionTrigger,
OneTimeCommissionThreshold: a.OneTimeCommissionThreshold,
EnableForceRecharge: a.EnableForceRecharge,
ForceRechargeAmount: a.ForceRechargeAmount,
ForceRechargeTriggerType: a.ForceRechargeTriggerType,
Status: a.Status,
CreatedAt: a.CreatedAt.Format(time.RFC3339),
UpdatedAt: a.UpdatedAt.Format(time.RFC3339),
}, nil
}
func (s *Service) createNewConfigVersion(ctx context.Context, allocation *model.ShopSeriesAllocation) error {
now := time.Now()
if err := s.configStore.InvalidateCurrent(ctx, allocation.ID, now); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "失效当前配置版本失败")
}
latestVersion, err := s.configStore.GetLatestVersion(ctx, allocation.ID)
newVersion := 1
if err == nil && latestVersion != nil {
newVersion = latestVersion.Version + 1
}
newConfig := &model.ShopSeriesAllocationConfig{
AllocationID: allocation.ID,
Version: newVersion,
BaseCommissionMode: allocation.BaseCommissionMode,
BaseCommissionValue: allocation.BaseCommissionValue,
EffectiveFrom: now,
}
if err := s.configStore.Create(ctx, newConfig); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "创建新配置版本失败")
}
return nil
}
func (s *Service) validateOneTimeCommissionConfig(req *dto.CreateShopSeriesAllocationRequest) error {
if !req.EnableOneTimeCommission {
return nil
}
if req.OneTimeCommissionConfig == nil {
return errors.New(errors.CodeInvalidParam, "启用一次性佣金时必须提供配置")
}
cfg := req.OneTimeCommissionConfig
if cfg.Type == model.OneTimeCommissionTypeFixed {
if cfg.Mode == "" {
return errors.New(errors.CodeInvalidParam, "固定类型一次性佣金必须指定返佣模式")
}
if cfg.Value <= 0 {
return errors.New(errors.CodeInvalidParam, "固定类型一次性佣金必须指定返佣金额")
}
} else if cfg.Type == model.OneTimeCommissionTypeTiered {
if len(cfg.Tiers) == 0 {
return errors.New(errors.CodeInvalidParam, "梯度类型一次性佣金必须提供梯度档位")
}
}
return nil
}
func (s *Service) validateOneTimeCommissionConfigForUpdate(enableOneTimeCommission bool, cfg *dto.OneTimeCommissionConfig) error {
if !enableOneTimeCommission {
return nil
}
if cfg == nil {
return nil
}
if cfg.Type == model.OneTimeCommissionTypeFixed {
if cfg.Mode == "" {
return errors.New(errors.CodeInvalidParam, "固定类型一次性佣金必须指定返佣模式")
}
if cfg.Value <= 0 {
return errors.New(errors.CodeInvalidParam, "固定类型一次性佣金必须指定返佣金额")
}
} else if cfg.Type == model.OneTimeCommissionTypeTiered {
if len(cfg.Tiers) == 0 {
return errors.New(errors.CodeInvalidParam, "梯度类型一次性佣金必须提供梯度档位")
}
}
return nil
}
func (s *Service) saveOneTimeCommissionTiers(ctx context.Context, allocationID uint, tiers []dto.OneTimeCommissionTierEntry, userID uint) error {
if len(tiers) == 0 {
return nil
}
tierModels := make([]*model.ShopSeriesOneTimeCommissionTier, len(tiers))
for i, t := range tiers {
tierModels[i] = &model.ShopSeriesOneTimeCommissionTier{
AllocationID: allocationID,
TierType: t.TierType,
ThresholdValue: t.Threshold,
CommissionMode: t.Mode,
CommissionValue: t.Value,
Status: constants.StatusEnabled,
}
tierModels[i].Creator = userID
}
return s.oneTimeCommissionTierStore.BatchCreate(ctx, tierModels)
}
func (s *Service) GetEffectiveConfig(ctx context.Context, allocationID uint, at time.Time) (*model.ShopSeriesAllocationConfig, error) {
config, err := s.configStore.GetEffective(ctx, allocationID, at)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "未找到生效的配置版本")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取生效配置失败")
}
return config, nil
}
func (s *Service) ListConfigVersions(ctx context.Context, allocationID uint) ([]*model.ShopSeriesAllocationConfig, error) {
_, err := s.allocationStore.GetByID(ctx, allocationID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "分配记录不存在")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取分配记录失败")
}
configs, err := s.configStore.List(ctx, allocationID)
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "获取配置版本列表失败")
}
return configs, nil
func (s *Service) GetByShopAndSeries(ctx context.Context, shopID, seriesID uint) (*model.ShopSeriesAllocation, error) {
return s.seriesAllocationStore.GetByShopAndSeries(ctx, shopID, seriesID)
}