重构: 店铺套餐分配系统从加价模式改为返佣模式
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:
@@ -37,6 +37,18 @@ func (s *PackageSeriesStore) GetByCode(ctx context.Context, code string) (*model
|
||||
return &series, nil
|
||||
}
|
||||
|
||||
// GetByIDs 批量查询套餐系列
|
||||
func (s *PackageSeriesStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.PackageSeries, error) {
|
||||
if len(ids) == 0 {
|
||||
return []*model.PackageSeries{}, nil
|
||||
}
|
||||
var seriesList []*model.PackageSeries
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", ids).Find(&seriesList).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return seriesList, nil
|
||||
}
|
||||
|
||||
func (s *PackageSeriesStore) Update(ctx context.Context, series *model.PackageSeries) error {
|
||||
return s.db.WithContext(ctx).Save(series).Error
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
)
|
||||
|
||||
type PackageStore struct {
|
||||
@@ -51,20 +53,29 @@ func (s *PackageStore) List(ctx context.Context, opts *store.QueryOptions, filte
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Package{})
|
||||
|
||||
// 代理用户额外过滤:只能看到已分配的套餐
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
shopID := middleware.GetShopIDFromContext(ctx)
|
||||
if userType == constants.UserTypeAgent && shopID > 0 {
|
||||
query = query.Joins("INNER JOIN tb_shop_package_allocation ON tb_shop_package_allocation.package_id = tb_package.id").
|
||||
Where("tb_shop_package_allocation.shop_id = ? AND tb_shop_package_allocation.status = ?",
|
||||
shopID, constants.StatusEnabled)
|
||||
}
|
||||
|
||||
if packageName, ok := filters["package_name"].(string); ok && packageName != "" {
|
||||
query = query.Where("package_name LIKE ?", "%"+packageName+"%")
|
||||
query = query.Where("tb_package.package_name LIKE ?", "%"+packageName+"%")
|
||||
}
|
||||
if seriesID, ok := filters["series_id"].(uint); ok && seriesID > 0 {
|
||||
query = query.Where("series_id = ?", seriesID)
|
||||
query = query.Where("tb_package.series_id = ?", seriesID)
|
||||
}
|
||||
if status, ok := filters["status"]; ok {
|
||||
query = query.Where("status = ?", status)
|
||||
query = query.Where("tb_package.status = ?", status)
|
||||
}
|
||||
if shelfStatus, ok := filters["shelf_status"]; ok {
|
||||
query = query.Where("shelf_status = ?", shelfStatus)
|
||||
query = query.Where("tb_package.shelf_status = ?", shelfStatus)
|
||||
}
|
||||
if packageType, ok := filters["package_type"].(string); ok && packageType != "" {
|
||||
query = query.Where("package_type = ?", packageType)
|
||||
query = query.Where("tb_package.package_type = ?", packageType)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShopPackageAllocationPriceHistoryStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewShopPackageAllocationPriceHistoryStore(db *gorm.DB) *ShopPackageAllocationPriceHistoryStore {
|
||||
return &ShopPackageAllocationPriceHistoryStore{db: db}
|
||||
}
|
||||
|
||||
func (s *ShopPackageAllocationPriceHistoryStore) Create(ctx context.Context, history *model.ShopPackageAllocationPriceHistory) error {
|
||||
return s.db.WithContext(ctx).Create(history).Error
|
||||
}
|
||||
|
||||
func (s *ShopPackageAllocationPriceHistoryStore) BatchCreate(ctx context.Context, histories []*model.ShopPackageAllocationPriceHistory) error {
|
||||
return s.db.WithContext(ctx).CreateInBatches(histories, 500).Error
|
||||
}
|
||||
|
||||
func (s *ShopPackageAllocationPriceHistoryStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.ShopPackageAllocationPriceHistory, int64, error) {
|
||||
var histories []*model.ShopPackageAllocationPriceHistory
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.ShopPackageAllocationPriceHistory{})
|
||||
|
||||
if allocationID, ok := filters["allocation_id"].(uint); ok && allocationID > 0 {
|
||||
query = query.Where("allocation_id = ?", allocationID)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
} else {
|
||||
query = query.Order("effective_from DESC")
|
||||
}
|
||||
|
||||
if err := query.Find(&histories).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return histories, total, nil
|
||||
}
|
||||
|
||||
func (s *ShopPackageAllocationPriceHistoryStore) ListByAllocation(ctx context.Context, allocationID uint) ([]*model.ShopPackageAllocationPriceHistory, error) {
|
||||
var histories []*model.ShopPackageAllocationPriceHistory
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Order("effective_from DESC").
|
||||
Find(&histories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return histories, nil
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShopSeriesAllocationConfigStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewShopSeriesAllocationConfigStore(db *gorm.DB) *ShopSeriesAllocationConfigStore {
|
||||
return &ShopSeriesAllocationConfigStore{db: db}
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationConfigStore) Create(ctx context.Context, config *model.ShopSeriesAllocationConfig) error {
|
||||
return s.db.WithContext(ctx).Create(config).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationConfigStore) GetEffective(ctx context.Context, allocationID uint, at time.Time) (*model.ShopSeriesAllocationConfig, error) {
|
||||
var config model.ShopSeriesAllocationConfig
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Where("effective_from <= ?", at).
|
||||
Where("effective_to IS NULL OR effective_to > ?", at).
|
||||
First(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationConfigStore) GetLatestVersion(ctx context.Context, allocationID uint) (*model.ShopSeriesAllocationConfig, error) {
|
||||
var config model.ShopSeriesAllocationConfig
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Order("version DESC").
|
||||
First(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationConfigStore) InvalidateCurrent(ctx context.Context, allocationID uint, effectiveTo time.Time) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.ShopSeriesAllocationConfig{}).
|
||||
Where("allocation_id = ? AND effective_to IS NULL", allocationID).
|
||||
Update("effective_to", effectiveTo).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationConfigStore) List(ctx context.Context, allocationID uint) ([]*model.ShopSeriesAllocationConfig, error) {
|
||||
var configs []*model.ShopSeriesAllocationConfig
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Order("version DESC").
|
||||
Find(&configs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestShopSeriesAllocationStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 1,
|
||||
SeriesID: 1,
|
||||
AllocatorShopID: 0,
|
||||
PricingMode: model.PricingModeFixed,
|
||||
PricingValue: 1000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, allocation)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, allocation.ID)
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 2,
|
||||
SeriesID: 2,
|
||||
AllocatorShopID: 0,
|
||||
PricingMode: model.PricingModePercent,
|
||||
PricingValue: 500,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
t.Run("查询存在的分配", func(t *testing.T) {
|
||||
result, err := s.GetByID(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, allocation.ShopID, result.ShopID)
|
||||
assert.Equal(t, allocation.SeriesID, result.SeriesID)
|
||||
assert.Equal(t, allocation.PricingMode, result.PricingMode)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的分配", func(t *testing.T) {
|
||||
_, err := s.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_GetByShopAndSeries(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 3,
|
||||
SeriesID: 3,
|
||||
AllocatorShopID: 0,
|
||||
PricingMode: model.PricingModeFixed,
|
||||
PricingValue: 2000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
t.Run("查询存在的店铺和系列组合", func(t *testing.T) {
|
||||
result, err := s.GetByShopAndSeries(ctx, 3, 3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, allocation.ID, result.ID)
|
||||
assert.Equal(t, uint(3), result.ShopID)
|
||||
assert.Equal(t, uint(3), result.SeriesID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的组合", func(t *testing.T) {
|
||||
_, err := s.GetByShopAndSeries(ctx, 99, 99)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_Update(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 4,
|
||||
SeriesID: 4,
|
||||
AllocatorShopID: 0,
|
||||
PricingMode: model.PricingModeFixed,
|
||||
PricingValue: 1500,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
allocation.PricingValue = 2500
|
||||
allocation.PricingMode = model.PricingModePercent
|
||||
err := s.Update(ctx, allocation)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2500), updated.PricingValue)
|
||||
assert.Equal(t, model.PricingModePercent, updated.PricingMode)
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_Delete(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 5,
|
||||
SeriesID: 5,
|
||||
AllocatorShopID: 0,
|
||||
PricingMode: model.PricingModeFixed,
|
||||
PricingValue: 1000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
err := s.Delete(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.GetByID(ctx, allocation.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocations := []*model.ShopSeriesAllocation{
|
||||
{ShopID: 10, SeriesID: 10, AllocatorShopID: 0, PricingMode: model.PricingModeFixed, PricingValue: 1000, Status: constants.StatusEnabled},
|
||||
{ShopID: 11, SeriesID: 11, AllocatorShopID: 0, PricingMode: model.PricingModePercent, PricingValue: 500, Status: constants.StatusEnabled},
|
||||
{ShopID: 12, SeriesID: 12, AllocatorShopID: 1, PricingMode: model.PricingModeFixed, PricingValue: 2000, Status: constants.StatusEnabled},
|
||||
}
|
||||
for _, a := range allocations {
|
||||
require.NoError(t, s.Create(ctx, a))
|
||||
}
|
||||
// 显式更新第三个分配为禁用状态
|
||||
allocations[2].Status = constants.StatusDisabled
|
||||
require.NoError(t, s.Update(ctx, allocations[2]))
|
||||
|
||||
t.Run("查询所有分配", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.GreaterOrEqual(t, len(result), 3)
|
||||
})
|
||||
|
||||
t.Run("按店铺ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"shop_id": uint(10)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, uint(10), a.ShopID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按系列ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"series_id": uint(11)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, uint(11), a.SeriesID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按分配者店铺ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"allocator_shop_id": uint(1)}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(1))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, uint(1), a.AllocatorShopID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-启用状态值为1", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": 1}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, 1, a.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按状态过滤-启用", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"status": constants.StatusEnabled}
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(2))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, constants.StatusEnabled, a.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, total, int64(3))
|
||||
assert.LessOrEqual(t, len(result), 2)
|
||||
})
|
||||
|
||||
t.Run("默认分页选项", func(t *testing.T) {
|
||||
result, _, err := s.List(ctx, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_UpdateStatus(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 20,
|
||||
SeriesID: 20,
|
||||
AllocatorShopID: 0,
|
||||
PricingMode: model.PricingModeFixed,
|
||||
PricingValue: 1000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
err := s.UpdateStatus(ctx, allocation.ID, constants.StatusDisabled, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.StatusDisabled, updated.Status)
|
||||
assert.Equal(t, uint(1), updated.Updater)
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_HasDependentAllocations(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 30,
|
||||
SeriesID: 30,
|
||||
AllocatorShopID: 100,
|
||||
PricingMode: model.PricingModeFixed,
|
||||
PricingValue: 1000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
t.Run("检查存在的依赖分配", func(t *testing.T) {
|
||||
// 注意:这个测试依赖于数据库中存在特定的店铺层级关系
|
||||
// 由于测试环境可能没有这样的关系,我们只验证函数可以执行
|
||||
has, err := s.HasDependentAllocations(ctx, 100, 30)
|
||||
require.NoError(t, err)
|
||||
// 结果取决于数据库中的实际店铺关系
|
||||
assert.IsType(t, true, has)
|
||||
})
|
||||
|
||||
t.Run("检查不存在的依赖分配", func(t *testing.T) {
|
||||
has, err := s.HasDependentAllocations(ctx, 99999, 99999)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShopSeriesCommissionStatsStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewShopSeriesCommissionStatsStore(db *gorm.DB) *ShopSeriesCommissionStatsStore {
|
||||
return &ShopSeriesCommissionStatsStore{db: db}
|
||||
}
|
||||
|
||||
func (s *ShopSeriesCommissionStatsStore) Create(ctx context.Context, stats *model.ShopSeriesCommissionStats) error {
|
||||
return s.db.WithContext(ctx).Create(stats).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesCommissionStatsStore) GetCurrent(ctx context.Context, allocationID uint, periodType string, now time.Time) (*model.ShopSeriesCommissionStats, error) {
|
||||
var stats model.ShopSeriesCommissionStats
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Where("period_type = ?", periodType).
|
||||
Where("period_start <= ? AND period_end >= ?", now, now).
|
||||
Where("status = ?", model.StatsStatusActive).
|
||||
First(&stats).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesCommissionStatsStore) Update(ctx context.Context, stats *model.ShopSeriesCommissionStats) error {
|
||||
return s.db.WithContext(ctx).Save(stats).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesCommissionStatsStore) IncrementSales(ctx context.Context, id uint, salesCount int64, salesAmount int64, version int) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.ShopSeriesCommissionStats{}).
|
||||
Where("id = ? AND version = ?", id, version).
|
||||
Updates(map[string]interface{}{
|
||||
"total_sales_count": gorm.Expr("total_sales_count + ?", salesCount),
|
||||
"total_sales_amount": gorm.Expr("total_sales_amount + ?", salesAmount),
|
||||
"last_updated_at": time.Now(),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesCommissionStatsStore) CompletePeriod(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.ShopSeriesCommissionStats{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", model.StatsStatusCompleted).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesCommissionStatsStore) ListExpired(ctx context.Context, before time.Time) ([]*model.ShopSeriesCommissionStats, error) {
|
||||
var stats []*model.ShopSeriesCommissionStats
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("period_end < ?", before).
|
||||
Where("status = ?", model.StatsStatusActive).
|
||||
Find(&stats).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
Reference in New Issue
Block a user