重构: 店铺套餐分配系统从加价模式改为返佣模式
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m18s
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:
@@ -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,
|
||||
|
||||
217
tests/integration/shop_package_batch_allocation_test.go
Normal file
217
tests/integration/shop_package_batch_allocation_test.go
Normal 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
|
||||
}
|
||||
225
tests/integration/shop_package_batch_pricing_test.go
Normal file
225
tests/integration/shop_package_batch_pricing_test.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user