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

@@ -377,7 +377,6 @@ func TestDevice_BatchSetSeriesBinding(t *testing.T) {
t.Run("设置禁用的系列-应失败", func(t *testing.T) {
disabledSeries := createTestPackageSeries(t, env, "禁用系列")
env.TX.Model(&model.PackageSeries{}).Where("id = ?", disabledSeries.ID).Update("status", constants.StatusDisabled)
env.TX.Model(&model.ShopSeriesAllocation{}).Where("id = ?", disabledSeries.ID).Update("status", constants.StatusDisabled)
body := map[string]interface{}{
"device_ids": []uint{devices[2].ID},

View File

@@ -672,7 +672,6 @@ func TestIotCard_BatchSetSeriesBinding(t *testing.T) {
// 创建一个禁用的分配
disabledSeries := createTestPackageSeries(t, env, "禁用系列")
env.TX.Model(&model.PackageSeries{}).Where("id = ?", disabledSeries.ID).Update("status", constants.StatusDisabled)
env.TX.Model(&model.ShopSeriesAllocation{}).Where("id = ?", disabledSeries.ID).Update("status", constants.StatusDisabled)
body := map[string]interface{}{
"iccids": []string{cards[2].ICCID},
@@ -799,3 +798,64 @@ func TestIotCard_BatchSetSeriesBinding(t *testing.T) {
}
})
}
func createTestPackageSeries(t *testing.T, env *integ.IntegrationTestEnv, name string) *model.PackageSeries {
t.Helper()
timestamp := time.Now().UnixNano()
series := &model.PackageSeries{
SeriesCode: fmt.Sprintf("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 createTestAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, seriesID, allocatorShopID uint) *model.ShopPackageAllocation {
t.Helper()
timestamp := time.Now().UnixNano()
pkg := &model.Package{
PackageCode: fmt.Sprintf("PKG_%d", timestamp),
PackageName: "测试套餐",
SeriesID: seriesID,
PackageType: "formal",
DurationMonths: 1,
RealDataMB: 1024,
CostPrice: 5000,
SuggestedRetailPrice: 12800,
Status: constants.StatusEnabled,
ShelfStatus: 1,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(pkg).Error
require.NoError(t, err, "创建测试套餐失败")
allocation := &model.ShopPackageAllocation{
ShopID: shopID,
PackageID: pkg.ID,
AllocatorShopID: allocatorShopID,
CostPrice: 5000,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err = env.TX.Create(allocation).Error
require.NoError(t, err, "创建测试分配失败")
return allocation
}

View File

@@ -1,253 +0,0 @@
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 TestMyPackageAPI_ListMyPackages(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
parentShop := env.CreateTestShop("一级店铺", 1, nil)
childShop := env.CreateTestShop("二级店铺", 2, &parentShop.ID)
agentAccount := env.CreateTestAccount("agent_my_pkg", "password123", constants.UserTypeAgent, &childShop.ID, nil)
series := createTestPackageSeriesForMyPkg(t, env, "测试系列")
pkg := createTestPackageForMyPkg(t, env, series.ID, "测试套餐")
createTestAllocationForMyPkg(t, env, parentShop.ID, series.ID, 0)
createTestAllocationForMyPkg(t, env, childShop.ID, series.ID, parentShop.ID)
t.Run("代理查看可售套餐列表", func(t *testing.T) {
resp, err := env.AsUser(agentAccount).Request("GET", "/api/admin/my-packages?page=1&page_size=20", 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, "应返回成功: %s", result.Message)
t.Logf("ListMyPackages response: %+v", result.Data)
})
t.Run("按系列ID筛选", func(t *testing.T) {
url := fmt.Sprintf("/api/admin/my-packages?series_id=%d", series.ID)
resp, err := env.AsUser(agentAccount).Request("GET", url, nil)
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) {
resp, err := env.AsUser(agentAccount).Request("GET", "/api/admin/my-packages?package_type=formal", nil)
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)
})
_ = pkg
}
func TestMyPackageAPI_GetMyPackage(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
parentShop := env.CreateTestShop("一级店铺", 1, nil)
childShop := env.CreateTestShop("二级店铺", 2, &parentShop.ID)
agentAccount := env.CreateTestAccount("agent_get_pkg", "password123", constants.UserTypeAgent, &childShop.ID, nil)
series := createTestPackageSeriesForMyPkg(t, env, "测试系列")
pkg := createTestPackageForMyPkg(t, env, series.ID, "测试套餐")
createTestAllocationForMyPkg(t, env, parentShop.ID, series.ID, 0)
createTestAllocationForMyPkg(t, env, childShop.ID, series.ID, parentShop.ID)
t.Run("获取可售套餐详情", func(t *testing.T) {
url := fmt.Sprintf("/api/admin/my-packages/%d", pkg.ID)
resp, err := env.AsUser(agentAccount).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, "应返回成功: %s", result.Message)
if result.Data != nil {
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, float64(pkg.ID), dataMap["id"])
t.Logf("套餐详情: %+v", dataMap)
}
})
t.Run("获取不存在的套餐", func(t *testing.T) {
resp, err := env.AsUser(agentAccount).Request("GET", "/api/admin/my-packages/999999", nil)
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 TestMyPackageAPI_ListMySeriesAllocations(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
parentShop := env.CreateTestShop("一级店铺", 1, nil)
childShop := env.CreateTestShop("二级店铺", 2, &parentShop.ID)
agentAccount := env.CreateTestAccount("agent_series_alloc", "password123", constants.UserTypeAgent, &childShop.ID, nil)
series1 := createTestPackageSeriesForMyPkg(t, env, "系列1")
series2 := createTestPackageSeriesForMyPkg(t, env, "系列2")
createTestAllocationForMyPkg(t, env, parentShop.ID, series1.ID, 0)
createTestAllocationForMyPkg(t, env, childShop.ID, series1.ID, parentShop.ID)
createTestAllocationForMyPkg(t, env, parentShop.ID, series2.ID, 0)
createTestAllocationForMyPkg(t, env, childShop.ID, series2.ID, parentShop.ID)
t.Run("获取被分配的系列列表", func(t *testing.T) {
resp, err := env.AsUser(agentAccount).Request("GET", "/api/admin/my-series-allocations?page=1&page_size=20", 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, "应返回成功: %s", result.Message)
t.Logf("ListMySeriesAllocations response: %+v", result.Data)
})
t.Run("分页参数生效", func(t *testing.T) {
resp, err := env.AsUser(agentAccount).Request("GET", "/api/admin/my-series-allocations?page=1&page_size=1", nil)
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 TestMyPackageAPI_Auth(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
t.Run("未认证请求应返回错误", func(t *testing.T) {
resp, err := env.ClearAuth().Request("GET", "/api/admin/my-packages", nil)
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, "未认证请求应返回错误码")
})
t.Run("未认证访问系列分配列表", func(t *testing.T) {
resp, err := env.ClearAuth().Request("GET", "/api/admin/my-series-allocations", nil)
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 createTestPackageSeriesForMyPkg(t *testing.T, env *integ.IntegrationTestEnv, name string) *model.PackageSeries {
t.Helper()
timestamp := time.Now().UnixNano()
series := &model.PackageSeries{
SeriesCode: fmt.Sprintf("SERIES_MY_%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 createTestPackageForMyPkg(t *testing.T, env *integ.IntegrationTestEnv, seriesID uint, name string) *model.Package {
t.Helper()
timestamp := time.Now().UnixNano()
pkg := &model.Package{
PackageCode: fmt.Sprintf("PKG_%d", timestamp),
PackageName: name,
SeriesID: seriesID,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 1024,
DataAmountMB: 1024,
Price: 9900,
SuggestedRetailPrice: 12800,
Status: constants.StatusEnabled,
ShelfStatus: 1,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(pkg).Error
require.NoError(t, err, "创建测试套餐失败")
return pkg
}
func createTestAllocationForMyPkg(t *testing.T, env *integ.IntegrationTestEnv, shopID, seriesID, allocatorShopID uint) *model.ShopSeriesAllocation {
t.Helper()
allocation := &model.ShopSeriesAllocation{
ShopID: shopID,
SeriesID: seriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: "fixed",
BaseCommissionValue: 500,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(allocation).Error
require.NoError(t, err, "创建测试分配失败")
return allocation
}

View File

@@ -416,7 +416,6 @@ func TestPackageAPI_Get(t *testing.T) {
PackageName: "测试套餐",
PackageType: "formal",
DurationMonths: 12,
Price: 99900,
Status: constants.StatusEnabled,
ShelfStatus: 2,
BaseModel: model.BaseModel{
@@ -452,7 +451,6 @@ func TestPackageAPI_List(t *testing.T) {
PackageName: "列表测试套餐1",
PackageType: "formal",
DurationMonths: 12,
Price: 99900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
BaseModel: model.BaseModel{Creator: 1},
@@ -462,7 +460,6 @@ func TestPackageAPI_List(t *testing.T) {
PackageName: "列表测试套餐2",
PackageType: "addon",
DurationMonths: 1,
Price: 9990,
Status: constants.StatusEnabled,
ShelfStatus: 2,
BaseModel: model.BaseModel{Creator: 1},
@@ -495,7 +492,6 @@ func TestPackageAPI_Update(t *testing.T) {
PackageName: "原始套餐名称",
PackageType: "formal",
DurationMonths: 12,
Price: 99900,
Status: constants.StatusEnabled,
ShelfStatus: 2,
BaseModel: model.BaseModel{
@@ -538,7 +534,6 @@ func TestPackageAPI_Delete(t *testing.T) {
PackageName: "测试套餐",
PackageType: "formal",
DurationMonths: 12,
Price: 99900,
Status: constants.StatusEnabled,
ShelfStatus: 2,
BaseModel: model.BaseModel{

View File

@@ -31,7 +31,6 @@ func TestBatchAllocationAPI_Create(t *testing.T) {
"mode": "fixed",
"value": 1000,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
@@ -59,7 +58,6 @@ func TestBatchAllocationAPI_Create(t *testing.T) {
"mode": "percent",
"value": 200,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
@@ -89,7 +87,6 @@ func TestBatchAllocationAPI_Create(t *testing.T) {
"mode": "fixed",
"value": 800,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
@@ -115,7 +112,7 @@ func TestBatchAllocationAPI_Create(t *testing.T) {
"mode": "percent",
"value": 150,
},
"enable_tier_commission": true,
"tier_config": map[string]interface{}{
"period_type": "monthly",
"tier_type": "sales_count",
@@ -149,7 +146,6 @@ func TestBatchAllocationAPI_Create(t *testing.T) {
"mode": "fixed",
"value": 1000,
},
"enable_tier_commission": false,
}
jsonBody, _ := json.Marshal(body)
@@ -192,15 +188,15 @@ func createBatchTestPackages(t *testing.T, env *integ.IntegrationTestEnv, series
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,
PackageCode: fmt.Sprintf("BATCH_PKG_%d_%d", timestamp, i),
PackageName: fmt.Sprintf("批量测试套餐%d", i+1),
SeriesID: seriesID,
PackageType: "formal",
DurationMonths: 1,
CostPrice: 5000 + int64(i*500),
SuggestedRetailPrice: 9900 + int64(i*1000),
Status: constants.StatusEnabled,
ShelfStatus: 1,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,

View File

@@ -179,15 +179,14 @@ func createPricingTestPackages(t *testing.T, env *integ.IntegrationTestEnv, seri
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,
PackageCode: fmt.Sprintf("PRICING_PKG_%d_%d", timestamp, i),
PackageName: fmt.Sprintf("调价测试套餐%d", i+1),
SeriesID: seriesID,
PackageType: "formal",
DurationMonths: 1,
CostPrice: 5000,
Status: constants.StatusEnabled,
ShelfStatus: 1,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
@@ -207,11 +206,11 @@ func createPricingTestAllocation(t *testing.T, env *integ.IntegrationTestEnv, sh
t.Helper()
allocation := &model.ShopPackageAllocation{
ShopID: shopID,
PackageID: packageID,
AllocationID: 0,
CostPrice: costPrice,
Status: constants.StatusEnabled,
ShopID: shopID,
PackageID: packageID,
AllocatorShopID: 0,
CostPrice: costPrice,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,

View File

@@ -1,579 +0,0 @@
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"
)
// ==================== 套餐系列分配 API 测试 ====================
func TestShopSeriesAllocationAPI_Create(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("一级店铺", 1, nil)
series := createTestPackageSeries(t, env, "测试系列")
t.Run("平台为一级店铺分配套餐系列", func(t *testing.T) {
body := map[string]interface{}{
"shop_id": shop.ID,
"series_id": series.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-series-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)
if result.Data != nil {
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, float64(shop.ID), dataMap["shop_id"])
assert.Equal(t, float64(series.ID), dataMap["series_id"])
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"])
}
})
t.Run("一级店铺为二级店铺分配套餐系列", func(t *testing.T) {
parentShop := env.CreateTestShop("另一个一级店铺", 1, nil)
childShop := env.CreateTestShop("二级店铺", 2, &parentShop.ID)
agentAccount := env.CreateTestAccount("agent_create", "password123", constants.UserTypeAgent, &parentShop.ID, nil)
series2 := createTestPackageSeries(t, env, "系列2")
createTestAllocation(t, env, parentShop.ID, series2.ID, 0)
body := map[string]interface{}{
"shop_id": childShop.ID,
"series_id": series2.ID,
"base_commission": map[string]interface{}{
"mode": "percent",
"value": 100,
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsUser(agentAccount).Request("POST", "/api/admin/shop-series-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) {
parent := env.CreateTestShop("父店铺", 1, nil)
child := env.CreateTestShop("子店铺", 2, &parent.ID)
series3 := createTestPackageSeries(t, env, "系列3")
body := map[string]interface{}{
"shop_id": child.ID,
"series_id": series3.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 500,
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-series-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, "平台不能为二级店铺分配")
})
t.Run("重复分配应失败", func(t *testing.T) {
newShop := env.CreateTestShop("新店铺", 1, nil)
series4 := createTestPackageSeries(t, env, "系列4")
createTestAllocation(t, env, newShop.ID, series4.ID, 0)
body := map[string]interface{}{
"shop_id": newShop.ID,
"series_id": series4.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-series-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 TestShopSeriesAllocationAPI_Get(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) {
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d", 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 := result.Data.(map[string]interface{})
assert.Equal(t, float64(allocation.ID), dataMap["id"])
assert.Equal(t, float64(shop.ID), dataMap["shop_id"])
})
t.Run("获取不存在的分配", func(t *testing.T) {
resp, err := env.AsSuperAdmin().Request("GET", "/api/admin/shop-series-allocations/999999", nil)
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 TestShopSeriesAllocationAPI_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)
t.Run("更新基础佣金", func(t *testing.T) {
body := map[string]interface{}{
"base_commission": map[string]interface{}{
"mode": "percent",
"value": 150,
},
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.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{})
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) {
enableTier := true
body := map[string]interface{}{
"enable_tier_commission": &enableTier,
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
resp, err := env.AsSuperAdmin().Request("PUT", 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 TestShopSeriesAllocationAPI_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)
t.Run("删除分配", func(t *testing.T) {
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.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)
getResp, err := env.AsSuperAdmin().Request("GET", url, nil)
require.NoError(t, err)
defer getResp.Body.Close()
var getResult response.Response
json.NewDecoder(getResp.Body).Decode(&getResult)
assert.NotEqual(t, 0, getResult.Code, "删除后应无法获取")
})
}
func TestShopSeriesAllocationAPI_List(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop1 := env.CreateTestShop("店铺1", 1, nil)
shop2 := env.CreateTestShop("店铺2", 1, nil)
series := createTestPackageSeries(t, env, "测试系列")
createTestAllocation(t, env, shop1.ID, series.ID, 0)
createTestAllocation(t, env, shop2.ID, series.ID, 0)
t.Run("获取分配列表", func(t *testing.T) {
resp, err := env.AsSuperAdmin().Request("GET", "/api/admin/shop-series-allocations?page=1&page_size=20", 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)
})
t.Run("按店铺ID筛选", func(t *testing.T) {
url := fmt.Sprintf("/api/admin/shop-series-allocations?shop_id=%d", shop1.ID)
resp, err := env.AsSuperAdmin().Request("GET", url, nil)
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("按系列ID筛选", func(t *testing.T) {
url := fmt.Sprintf("/api/admin/shop-series-allocations?series_id=%d", series.ID)
resp, err := env.AsSuperAdmin().Request("GET", url, nil)
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 TestShopSeriesAllocationAPI_UpdateStatus(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{}{
"status": constants.StatusDisabled,
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/status", allocation.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)
getURL := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
getResp, _ := env.AsSuperAdmin().Request("GET", getURL, nil)
defer getResp.Body.Close()
var getResult response.Response
json.NewDecoder(getResp.Body).Decode(&getResult)
dataMap := getResult.Data.(map[string]interface{})
assert.Equal(t, float64(constants.StatusDisabled), dataMap["status"])
})
t.Run("启用分配", func(t *testing.T) {
body := map[string]interface{}{
"status": constants.StatusEnabled,
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d/status", allocation.ID)
resp, err := env.AsSuperAdmin().Request("PUT", 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 TestShopSeriesAllocationAPI_OneTimeCommission(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
t.Run("创建分配-固定类型一次性佣金配置落库", func(t *testing.T) {
shop := env.CreateTestShop("一次性佣金测试店铺1", 1, nil)
series := createTestPackageSeries(t, env, "一次性佣金测试系列1")
body := map[string]interface{}{
"shop_id": shop.ID,
"series_id": series.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
"enable_one_time_commission": true,
"one_time_commission_config": map[string]interface{}{
"type": "fixed",
"trigger": "accumulated_recharge",
"threshold": 10000,
"mode": "fixed",
"value": 500,
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-series-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)
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, true, dataMap["enable_one_time_commission"])
if cfg, ok := dataMap["one_time_commission_config"].(map[string]interface{}); ok {
assert.Equal(t, "fixed", cfg["type"])
assert.Equal(t, "accumulated_recharge", cfg["trigger"])
assert.Equal(t, float64(10000), cfg["threshold"])
assert.Equal(t, "fixed", cfg["mode"])
assert.Equal(t, float64(500), cfg["value"])
} else {
t.Error("一次性佣金配置应返回")
}
})
t.Run("创建分配-梯度类型一次性佣金配置落库", func(t *testing.T) {
shop := env.CreateTestShop("一次性佣金测试店铺2", 1, nil)
series := createTestPackageSeries(t, env, "一次性佣金测试系列2")
body := map[string]interface{}{
"shop_id": shop.ID,
"series_id": series.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
"enable_one_time_commission": true,
"one_time_commission_config": map[string]interface{}{
"type": "tiered",
"trigger": "single_recharge",
"threshold": 5000,
"tiers": []map[string]interface{}{
{"tier_type": "sales_count", "threshold": 10, "mode": "fixed", "value": 100},
{"tier_type": "sales_count", "threshold": 50, "mode": "fixed", "value": 500},
{"tier_type": "sales_amount", "threshold": 100000, "mode": "percent", "value": 50},
},
},
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-series-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)
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, true, dataMap["enable_one_time_commission"])
if cfg, ok := dataMap["one_time_commission_config"].(map[string]interface{}); ok {
assert.Equal(t, "tiered", cfg["type"])
assert.Equal(t, "single_recharge", cfg["trigger"])
if tiers, ok := cfg["tiers"].([]interface{}); ok {
assert.Equal(t, 3, len(tiers), "应有3个梯度档位")
} else {
t.Error("梯度档位应返回")
}
}
})
t.Run("创建分配-启用一次性佣金但未提供配置应失败", func(t *testing.T) {
shop := env.CreateTestShop("一次性佣金测试店铺3", 1, nil)
series := createTestPackageSeries(t, env, "一次性佣金测试系列3")
body := map[string]interface{}{
"shop_id": shop.ID,
"series_id": series.ID,
"base_commission": map[string]interface{}{
"mode": "fixed",
"value": 1000,
},
"enable_one_time_commission": true,
}
jsonBody, _ := json.Marshal(body)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-series-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, "启用一次性佣金但未提供配置应失败")
})
t.Run("更新分配-更新一次性佣金配置", func(t *testing.T) {
shop := env.CreateTestShop("一次性佣金测试店铺4", 1, nil)
series := createTestPackageSeries(t, env, "一次性佣金测试系列4")
allocation := createTestAllocation(t, env, shop.ID, series.ID, 0)
enableOneTime := true
body := map[string]interface{}{
"enable_one_time_commission": &enableOneTime,
"one_time_commission_config": map[string]interface{}{
"type": "fixed",
"trigger": "accumulated_recharge",
"threshold": 20000,
"mode": "percent",
"value": 100,
},
}
jsonBody, _ := json.Marshal(body)
url := fmt.Sprintf("/api/admin/shop-series-allocations/%d", allocation.ID)
resp, err := env.AsSuperAdmin().Request("PUT", 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, "更新应成功: %s", result.Message)
dataMap := result.Data.(map[string]interface{})
assert.Equal(t, true, dataMap["enable_one_time_commission"])
if cfg, ok := dataMap["one_time_commission_config"].(map[string]interface{}); ok {
assert.Equal(t, float64(20000), cfg["threshold"])
assert.Equal(t, "percent", cfg["mode"])
assert.Equal(t, float64(100), cfg["value"])
}
})
}
// ==================== 梯度佣金 API 测试 ====================
// ==================== 权限测试 ====================
func TestShopSeriesAllocationAPI_Auth(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
t.Run("未认证请求应返回错误", func(t *testing.T) {
resp, err := env.ClearAuth().Request("GET", "/api/admin/shop-series-allocations", nil)
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, "未认证请求应返回错误码")
})
}
// ==================== 辅助函数 ====================
// createTestPackageSeries 创建测试套餐系列
func createTestPackageSeries(t *testing.T, env *integ.IntegrationTestEnv, name string) *model.PackageSeries {
t.Helper()
timestamp := time.Now().UnixNano()
series := &model.PackageSeries{
SeriesCode: fmt.Sprintf("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
}
// createTestAllocation 创建测试分配
func createTestAllocation(t *testing.T, env *integ.IntegrationTestEnv, shopID, seriesID, allocatorShopID uint) *model.ShopSeriesAllocation {
t.Helper()
allocation := &model.ShopSeriesAllocation{
ShopID: shopID,
SeriesID: seriesID,
AllocatorShopID: allocatorShopID,
BaseCommissionMode: "fixed",
BaseCommissionValue: 1000,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := env.TX.Create(allocation).Error
require.NoError(t, err, "创建测试分配失败")
return allocation
}