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

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

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

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

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

View File

@@ -234,12 +234,13 @@ func createTestAllocationForMyPkg(t *testing.T, env *integ.IntegrationTestEnv, s
t.Helper()
allocation := &model.ShopSeriesAllocation{
ShopID: shopID,
SeriesID: seriesID,
AllocatorShopID: allocatorShopID,
PricingMode: model.PricingModeFixed,
PricingValue: 500,
Status: constants.StatusEnabled,
ShopID: shopID,
SeriesID: seriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: "fixed",
BaseCommissionValue: 500,
EnableTierCommission: false,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,

View File

@@ -0,0 +1,217 @@
package integration
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/response"
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBatchAllocationAPI_Create(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
parentShop := env.CreateTestShop("父级店铺", 1, nil)
childShop := env.CreateTestShop("子级店铺", 2, &parentShop.ID)
series := createBatchTestPackageSeries(t, env, "批量分配测试系列")
createBatchTestPackages(t, env, series.ID, 3)
t.Run("批量分配套餐_固定金额返佣", func(t *testing.T) {
body := map[string]interface{}{
"shop_id": childShop.ID,
"series_id": series.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-allocations", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code, "应返回成功: %s", result.Message)
})
t.Run("批量分配套餐_百分比返佣", func(t *testing.T) {
series2 := createBatchTestPackageSeries(t, env, "系列2")
createBatchTestPackages(t, env, series2.ID, 2)
shop2 := env.CreateTestShop("测试店铺2", 1, nil)
body := map[string]interface{}{
"shop_id": shop2.ID,
"series_id": series2.ID,
"base_commission": map[string]interface{}{
"mode": "percent",
"value": 200,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-allocations", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
})
t.Run("批量分配_带可选加价", func(t *testing.T) {
series3 := createBatchTestPackageSeries(t, env, "系列3")
createBatchTestPackages(t, env, series3.ID, 2)
shop3 := env.CreateTestShop("测试店铺3", 1, nil)
body := map[string]interface{}{
"shop_id": shop3.ID,
"series_id": series3.ID,
"price_adjustment": map[string]interface{}{
"type": "fixed",
"value": 500,
},
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 800,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-allocations", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
})
t.Run("批量分配_启用梯度返佣", func(t *testing.T) {
series4 := createBatchTestPackageSeries(t, env, "系列4")
createBatchTestPackages(t, env, series4.ID, 2)
shop4 := env.CreateTestShop("测试店铺4", 1, nil)
body := map[string]interface{}{
"shop_id": shop4.ID,
"series_id": series4.ID,
"base_commission": map[string]interface{}{
"mode": "percent",
"value": 150,
},
"enable_tier_commission": true,
"tier_config": map[string]interface{}{
"period_type": "monthly",
"tier_type": "sales_count",
"tiers": []map[string]interface{}{
{"threshold": 100, "mode": "percent", "value": 200},
{"threshold": 200, "mode": "percent", "value": 250},
{"threshold": 500, "mode": "percent", "value": 300},
},
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-allocations", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code, "启用梯度返佣应成功: %s", result.Message)
})
t.Run("批量分配_系列无套餐应失败", func(t *testing.T) {
emptySeries := createBatchTestPackageSeries(t, env, "空系列")
shop5 := env.CreateTestShop("测试店铺5", 1, nil)
body := map[string]interface{}{
"shop_id": shop5.ID,
"series_id": emptySeries.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-allocations", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.NotEqual(t, 0, result.Code, "空系列应返回错误")
})
}
func createBatchTestPackageSeries(t *testing.T, env *integ.IntegrationTestEnv, name string) *model.PackageSeries {
t.Helper()
timestamp := time.Now().UnixNano()
series := &model.PackageSeries{
SeriesCode: fmt.Sprintf("BATCH_SERIES_%d", timestamp),
SeriesName: name,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(series).Error
require.NoError(t, err, "创建测试套餐系列失败")
return series
}
func createBatchTestPackages(t *testing.T, env *integ.IntegrationTestEnv, seriesID uint, count int) []*model.Package {
t.Helper()
packages := make([]*model.Package, 0, count)
timestamp := time.Now().UnixNano()
for i := 0; i < count; i++ {
pkg := &model.Package{
PackageCode: fmt.Sprintf("BATCH_PKG_%d_%d", timestamp, i),
PackageName: fmt.Sprintf("批量测试套餐%d", i+1),
SeriesID: seriesID,
PackageType: "formal",
DurationMonths: 1,
Price: 9900 + int64(i*1000),
SuggestedCostPrice: 5000 + int64(i*500),
Status: constants.StatusEnabled,
ShelfStatus: 1,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(pkg).Error
require.NoError(t, err, "创建测试套餐失败")
packages = append(packages, pkg)
}
return packages
}

View File

@@ -0,0 +1,225 @@
package integration
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/response"
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBatchPricingAPI_Update(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("测试店铺", 1, nil)
series := createPricingTestPackageSeries(t, env, "调价测试系列")
packages := createPricingTestPackages(t, env, series.ID, 3)
for _, pkg := range packages {
createPricingTestAllocation(t, env, shop.ID, pkg.ID, series.ID, 5000)
}
t.Run("批量调价_固定金额调整", func(t *testing.T) {
body := map[string]interface{}{
"shop_id": shop.ID,
"series_id": series.ID,
"price_adjustment": map[string]interface{}{
"type": "fixed",
"value": 1000,
},
"change_reason": "统一调价测试",
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-pricing", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code, "应返回成功: %s", result.Message)
if result.Data != nil {
dataMap := result.Data.(map[string]interface{})
updatedCount := int(dataMap["updated_count"].(float64))
assert.Equal(t, 3, updatedCount, "应更新3个套餐分配")
}
})
t.Run("批量调价_百分比调整", func(t *testing.T) {
shop2 := env.CreateTestShop("测试店铺2", 1, nil)
series2 := createPricingTestPackageSeries(t, env, "系列2")
packages2 := createPricingTestPackages(t, env, series2.ID, 2)
for _, pkg := range packages2 {
createPricingTestAllocation(t, env, shop2.ID, pkg.ID, series2.ID, 10000)
}
body := map[string]interface{}{
"shop_id": shop2.ID,
"series_id": series2.ID,
"price_adjustment": map[string]interface{}{
"type": "percent",
"value": 100,
},
"change_reason": "加价10%",
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-pricing", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
if result.Data != nil {
dataMap := result.Data.(map[string]interface{})
updatedCount := int(dataMap["updated_count"].(float64))
assert.Equal(t, 2, updatedCount, "应更新2个套餐分配")
}
})
t.Run("批量调价_不指定系列调整所有", func(t *testing.T) {
shop3 := env.CreateTestShop("测试店铺3", 1, nil)
series3a := createPricingTestPackageSeries(t, env, "系列3A")
series3b := createPricingTestPackageSeries(t, env, "系列3B")
pkg3a := createPricingTestPackages(t, env, series3a.ID, 1)[0]
pkg3b := createPricingTestPackages(t, env, series3b.ID, 1)[0]
createPricingTestAllocation(t, env, shop3.ID, pkg3a.ID, series3a.ID, 8000)
createPricingTestAllocation(t, env, shop3.ID, pkg3b.ID, series3b.ID, 8000)
body := map[string]interface{}{
"shop_id": shop3.ID,
"price_adjustment": map[string]interface{}{
"type": "fixed",
"value": 500,
},
"change_reason": "全局调价",
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-pricing", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
if result.Data != nil {
dataMap := result.Data.(map[string]interface{})
updatedCount := int(dataMap["updated_count"].(float64))
assert.GreaterOrEqual(t, updatedCount, 2, "应更新至少2个套餐分配")
}
})
t.Run("批量调价_无匹配记录应失败", func(t *testing.T) {
shop4 := env.CreateTestShop("空店铺", 1, nil)
body := map[string]interface{}{
"shop_id": shop4.ID,
"price_adjustment": map[string]interface{}{
"type": "fixed",
"value": 1000,
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-package-batch-pricing", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.NotEqual(t, 0, result.Code, "无匹配记录应返回错误")
})
}
func createPricingTestPackageSeries(t *testing.T, env *integ.IntegrationTestEnv, name string) *model.PackageSeries {
t.Helper()
timestamp := time.Now().UnixNano()
series := &model.PackageSeries{
SeriesCode: fmt.Sprintf("PRICING_SERIES_%d", timestamp),
SeriesName: name,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(series).Error
require.NoError(t, err)
return series
}
func createPricingTestPackages(t *testing.T, env *integ.IntegrationTestEnv, seriesID uint, count int) []*model.Package {
t.Helper()
packages := make([]*model.Package, 0, count)
timestamp := time.Now().UnixNano()
for i := 0; i < count; i++ {
pkg := &model.Package{
PackageCode: fmt.Sprintf("PRICING_PKG_%d_%d", timestamp, i),
PackageName: fmt.Sprintf("调价测试套餐%d", i+1),
SeriesID: seriesID,
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
SuggestedCostPrice: 5000,
Status: constants.StatusEnabled,
ShelfStatus: 1,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(pkg).Error
require.NoError(t, err)
packages = append(packages, pkg)
}
return packages
}
func createPricingTestAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, packageID, seriesID uint, costPrice int64) *model.ShopPackageAllocation {
t.Helper()
allocation := &model.ShopPackageAllocation{
ShopID: shopID,
PackageID: packageID,
AllocationID: 0,
CostPrice: costPrice,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(allocation).Error
require.NoError(t, err)
return allocation
}

View File

@@ -24,10 +24,12 @@ func TestShopSeriesAllocationAPI_Create(t *testing.T) {
t.Run("平台为一级店铺分配套餐系列", func(t *testing.T) {
body := map[string]interface{}{
"shop_id": shop.ID,
"series_id": series.ID,
"pricing_mode": "fixed",
"pricing_value": 1000,
"shop_id": shop.ID,
"series_id": series.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
}
jsonBody, _ := json.Marshal(body)
@@ -46,8 +48,10 @@ func TestShopSeriesAllocationAPI_Create(t *testing.T) {
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, float64(shop.ID), dataMap["shop_id"])
assert.Equal(t, float64(series.ID), dataMap["series_id"])
assert.Equal(t, "fixed", dataMap["pricing_mode"])
assert.Equal(t, float64(1000), dataMap["pricing_value"])
if baseComm, ok := dataMap["base_commission"].(map[string]interface{}); ok {
assert.Equal(t, "fixed", baseComm["mode"])
assert.Equal(t, float64(1000), baseComm["value"])
}
t.Logf("创建的分配 ID: %v", dataMap["id"])
}
})
@@ -60,13 +64,12 @@ func TestShopSeriesAllocationAPI_Create(t *testing.T) {
createTestAllocation(t, env, parentShop.ID, series2.ID, 0)
body := map[string]interface{}{
"shop_id": childShop.ID,
"series_id": series2.ID,
"pricing_mode": "percent",
"pricing_value": 100,
"one_time_commission_trigger": "one_time_recharge",
"one_time_commission_threshold": 10000,
"one_time_commission_amount": 500,
"shop_id": childShop.ID,
"series_id": series2.ID,
"base_commission": map[string]interface{}{
"mode": "percent",
"value": 100,
},
}
jsonBody, _ := json.Marshal(body)
@@ -85,10 +88,12 @@ func TestShopSeriesAllocationAPI_Create(t *testing.T) {
child := env.CreateTestShop("子店铺", 2, &parent.ID)
series3 := createTestPackageSeries(t, env, "系列3")
body := map[string]interface{}{
"shop_id": child.ID,
"series_id": series3.ID,
"pricing_mode": "fixed",
"pricing_value": 500,
"shop_id": child.ID,
"series_id": series3.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 500,
},
}
jsonBody, _ := json.Marshal(body)
@@ -108,10 +113,12 @@ func TestShopSeriesAllocationAPI_Create(t *testing.T) {
createTestAllocation(t, env, newShop.ID, series4.ID, 0)
body := map[string]interface{}{
"shop_id": newShop.ID,
"series_id": series4.ID,
"pricing_mode": "fixed",
"pricing_value": 1000,
"shop_id": newShop.ID,
"series_id": series4.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
}
jsonBody, _ := json.Marshal(body)
@@ -170,10 +177,12 @@ func TestShopSeriesAllocationAPI_Update(t *testing.T) {
series := createTestPackageSeries(t, env, "测试系列")
allocation := createTestAllocation(t, env, shop.ID, series.ID, 0)
t.Run("更新加价模式和值", func(t *testing.T) {
t.Run("更新基础佣金", func(t *testing.T) {
body := map[string]interface{}{
"pricing_mode": "percent",
"pricing_value": 150,
"base_commission": map[string]interface{}{
"mode": "percent",
"value": 150,
},
}
jsonBody, _ := json.Marshal(body)
@@ -190,15 +199,16 @@ func TestShopSeriesAllocationAPI_Update(t *testing.T) {
assert.Equal(t, 0, result.Code)
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, "percent", dataMap["pricing_mode"])
assert.Equal(t, float64(150), dataMap["pricing_value"])
if baseComm, ok := dataMap["base_commission"].(map[string]interface{}); ok {
assert.Equal(t, "percent", baseComm["mode"])
assert.Equal(t, float64(150), baseComm["value"])
}
})
t.Run("更新一次性佣金配置", func(t *testing.T) {
t.Run("启用梯度佣金", func(t *testing.T) {
enableTier := true
body := map[string]interface{}{
"one_time_commission_trigger": "accumulated_recharge",
"one_time_commission_threshold": 50000,
"one_time_commission_amount": 2000,
"enable_tier_commission": &enableTier,
}
jsonBody, _ := json.Marshal(body)
@@ -346,195 +356,6 @@ func TestShopSeriesAllocationAPI_UpdateStatus(t *testing.T) {
// ==================== 梯度佣金 API 测试 ====================
func TestCommissionTierAPI_Add(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("一级店铺", 1, nil)
series := createTestPackageSeries(t, env, "测试系列")
allocation := createTestAllocation(t, env, shop.ID, series.ID, 0)
t.Run("添加月度销量梯度佣金", func(t *testing.T) {
body := map[string]interface{}{
"tier_type": "sales_count",
"period_type": "monthly",
"threshold_value": 100,
"commission_amount": 1000,
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/tiers", allocation.ID)
resp, err := env.AsSuperAdmin().Request("POST", url, jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code, "应返回成功: %s", result.Message)
if result.Data != nil {
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, "sales_count", dataMap["tier_type"])
assert.Equal(t, "monthly", dataMap["period_type"])
assert.Equal(t, float64(100), dataMap["threshold_value"])
t.Logf("创建的梯度佣金 ID: %v", dataMap["id"])
}
})
t.Run("添加年度销售额梯度佣金", func(t *testing.T) {
body := map[string]interface{}{
"tier_type": "sales_amount",
"period_type": "yearly",
"threshold_value": 10000000,
"commission_amount": 50000,
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/tiers", allocation.ID)
resp, err := env.AsSuperAdmin().Request("POST", url, jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
})
t.Run("添加自定义周期梯度佣金", func(t *testing.T) {
body := map[string]interface{}{
"tier_type": "sales_count",
"period_type": "custom",
"period_start_date": "2026-01-01",
"period_end_date": "2026-06-30",
"threshold_value": 500,
"commission_amount": 5000,
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/tiers", allocation.ID)
resp, err := env.AsSuperAdmin().Request("POST", url, jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
})
}
func TestCommissionTierAPI_List(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("一级店铺", 1, nil)
series := createTestPackageSeries(t, env, "测试系列")
allocation := createTestAllocation(t, env, shop.ID, series.ID, 0)
createTestCommissionTier(t, env, allocation.ID, "sales_count", "monthly", 50, 500)
createTestCommissionTier(t, env, allocation.ID, "sales_count", "monthly", 100, 1000)
t.Run("获取梯度佣金列表", func(t *testing.T) {
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/tiers", allocation.ID)
resp, err := env.AsSuperAdmin().Request("GET", url, nil)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
dataMap, ok := result.Data.(map[string]interface{})
if ok {
list := dataMap["list"].([]interface{})
assert.GreaterOrEqual(t, len(list), 2, "应至少有2个梯度佣金")
} else {
list := result.Data.([]interface{})
assert.GreaterOrEqual(t, len(list), 2, "应至少有2个梯度佣金")
}
})
}
func TestCommissionTierAPI_Update(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("一级店铺", 1, nil)
series := createTestPackageSeries(t, env, "测试系列")
allocation := createTestAllocation(t, env, shop.ID, series.ID, 0)
tier := createTestCommissionTier(t, env, allocation.ID, "sales_count", "monthly", 50, 500)
t.Run("更新梯度佣金", func(t *testing.T) {
body := map[string]interface{}{
"threshold_value": 200,
"commission_amount": 2000,
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/tiers/%d", allocation.ID, tier.ID)
resp, err := env.AsSuperAdmin().Request("PUT", url, jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, float64(200), dataMap["threshold_value"])
assert.Equal(t, float64(2000), dataMap["commission_amount"])
})
}
func TestCommissionTierAPI_Delete(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("一级店铺", 1, nil)
series := createTestPackageSeries(t, env, "测试系列")
allocation := createTestAllocation(t, env, shop.ID, series.ID, 0)
tier := createTestCommissionTier(t, env, allocation.ID, "sales_count", "monthly", 50, 500)
t.Run("删除梯度佣金", func(t *testing.T) {
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/tiers/%d", allocation.ID, tier.ID)
resp, err := env.AsSuperAdmin().Request("DELETE", url, nil)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
listURL := fmt.Sprintf("/api/admin/shop-series-allocations/%d/tiers", allocation.ID)
listResp, _ := env.AsSuperAdmin().Request("GET", listURL, nil)
defer listResp.Body.Close()
var listResult response.Response
json.NewDecoder(listResp.Body).Decode(&listResult)
var list []interface{}
if dataMap, ok := listResult.Data.(map[string]interface{}); ok {
list = dataMap["list"].([]interface{})
} else {
list = listResult.Data.([]interface{})
}
for _, item := range list {
tierItem := item.(map[string]interface{})
assert.NotEqual(t, float64(tier.ID), tierItem["id"], "已删除的梯度不应出现在列表中")
}
})
}
// ==================== 权限测试 ====================
func TestShopSeriesAllocationAPI_Auth(t *testing.T) {
@@ -580,12 +401,13 @@ func createTestAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, s
t.Helper()
allocation := &model.ShopSeriesAllocation{
ShopID: shopID,
SeriesID: seriesID,
AllocatorShopID: allocatorShopID,
PricingMode: model.PricingModeFixed,
PricingValue: 1000,
Status: constants.StatusEnabled,
ShopID: shopID,
SeriesID: seriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: "fixed",
BaseCommissionValue: 1000,
EnableTierCommission: false,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
@@ -597,25 +419,3 @@ func createTestAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, s
return allocation
}
// createTestCommissionTier 创建测试梯度佣金
func createTestCommissionTier(t *testing.T, env *integ.IntegrationTestEnv, allocationID uint, tierType, periodType string, threshold, amount int64) *model.ShopSeriesCommissionTier {
t.Helper()
tier := &model.ShopSeriesCommissionTier{
AllocationID: allocationID,
TierType: tierType,
PeriodType: periodType,
ThresholdValue: threshold,
CommissionAmount: amount,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(tier).Error
require.NoError(t, err, "创建测试梯度佣金失败")
return tier
}