refactor: 一次性佣金配置从套餐级别提升到系列级别
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s
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:
@@ -203,3 +203,12 @@ func (s *DeviceStore) ListBySeriesID(ctx context.Context, seriesID uint) ([]*mod
|
||||
}
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (s *DeviceStore) UpdateRechargeTrackingFields(ctx context.Context, deviceID uint, accumulatedJSON, triggeredJSON string) error {
|
||||
return s.db.WithContext(ctx).Model(&model.Device{}).
|
||||
Where("id = ?", deviceID).
|
||||
Updates(map[string]interface{}{
|
||||
"accumulated_recharge_by_series": accumulatedJSON,
|
||||
"first_recharge_triggered_by_series": triggeredJSON,
|
||||
}).Error
|
||||
}
|
||||
|
||||
@@ -401,3 +401,12 @@ func (s *IotCardStore) ListBySeriesID(ctx context.Context, seriesID uint) ([]*mo
|
||||
}
|
||||
return cards, nil
|
||||
}
|
||||
|
||||
func (s *IotCardStore) UpdateRechargeTrackingFields(ctx context.Context, cardID uint, accumulatedJSON, triggeredJSON string) error {
|
||||
return s.db.WithContext(ctx).Model(&model.IotCard{}).
|
||||
Where("id = ?", cardID).
|
||||
Updates(map[string]interface{}{
|
||||
"accumulated_recharge_by_series": accumulatedJSON,
|
||||
"first_recharge_triggered_by_series": triggeredJSON,
|
||||
}).Error
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ func (s *PackageSeriesStore) List(ctx context.Context, opts *store.QueryOptions,
|
||||
if status, ok := filters["status"]; ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if enableOneTime, ok := filters["enable_one_time_commission"].(bool); ok {
|
||||
query = query.Where("enable_one_time_commission = ?", enableOneTime)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
|
||||
@@ -18,17 +18,16 @@ func TestPackageStore_Create(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
pkg := &model.Package{
|
||||
PackageCode: "PKG_TEST_001",
|
||||
PackageName: "测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
PackageCode: "PKG_TEST_001",
|
||||
PackageName: "测试套餐",
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, pkg)
|
||||
@@ -47,12 +46,12 @@ func TestPackageStore_GetByID(t *testing.T) {
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 2048,
|
||||
DataAmountMB: 2048,
|
||||
Price: 19900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 2048,
|
||||
CostPrice: 19900,
|
||||
SuggestedRetailPrice: 25800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
@@ -79,12 +78,12 @@ func TestPackageStore_GetByCode(t *testing.T) {
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 3072,
|
||||
DataAmountMB: 3072,
|
||||
Price: 29900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 3072,
|
||||
CostPrice: 29900,
|
||||
SuggestedRetailPrice: 39800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
@@ -111,24 +110,24 @@ func TestPackageStore_Update(t *testing.T) {
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 4096,
|
||||
DataAmountMB: 4096,
|
||||
Price: 39900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 4096,
|
||||
CostPrice: 39900,
|
||||
SuggestedRetailPrice: 49800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
pkg.PackageName = "测试套餐4-更新"
|
||||
pkg.Price = 49900
|
||||
pkg.CostPrice = 49900
|
||||
err := s.Update(ctx, pkg)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.GetByID(ctx, pkg.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "测试套餐4-更新", updated.PackageName)
|
||||
assert.Equal(t, int64(49900), updated.Price)
|
||||
assert.Equal(t, int64(49900), updated.CostPrice)
|
||||
}
|
||||
|
||||
func TestPackageStore_Delete(t *testing.T) {
|
||||
@@ -142,12 +141,12 @@ func TestPackageStore_Delete(t *testing.T) {
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
@@ -170,12 +169,12 @@ func TestPackageStore_List(t *testing.T) {
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
},
|
||||
{
|
||||
PackageCode: "LIST_P_002",
|
||||
@@ -183,12 +182,12 @@ func TestPackageStore_List(t *testing.T) {
|
||||
SeriesID: 2,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 12,
|
||||
DataType: "real",
|
||||
RealDataMB: 10240,
|
||||
DataAmountMB: 10240,
|
||||
Price: 99900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 10240,
|
||||
CostPrice: 99900,
|
||||
SuggestedRetailPrice: 129800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
},
|
||||
{
|
||||
PackageCode: "LIST_P_003",
|
||||
@@ -196,12 +195,12 @@ func TestPackageStore_List(t *testing.T) {
|
||||
SeriesID: 3,
|
||||
PackageType: "addon",
|
||||
DurationMonths: 1,
|
||||
DataType: "virtual",
|
||||
VirtualDataMB: 5120,
|
||||
DataAmountMB: 5120,
|
||||
Price: 4900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 2,
|
||||
|
||||
VirtualDataMB: 5120,
|
||||
CostPrice: 4900,
|
||||
SuggestedRetailPrice: 6800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 2,
|
||||
},
|
||||
}
|
||||
for _, pkg := range pkgList {
|
||||
@@ -286,12 +285,12 @@ func TestPackageStore_UpdateStatus(t *testing.T) {
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
@@ -314,12 +313,12 @@ func TestPackageStore_UpdateShelfStatus(t *testing.T) {
|
||||
SeriesID: 1,
|
||||
PackageType: "formal",
|
||||
DurationMonths: 1,
|
||||
DataType: "real",
|
||||
RealDataMB: 1024,
|
||||
DataAmountMB: 1024,
|
||||
Price: 9900,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
|
||||
RealDataMB: 1024,
|
||||
CostPrice: 9900,
|
||||
SuggestedRetailPrice: 12800,
|
||||
Status: constants.StatusEnabled,
|
||||
ShelfStatus: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, pkg))
|
||||
|
||||
|
||||
@@ -56,8 +56,11 @@ func (s *ShopPackageAllocationStore) List(ctx context.Context, opts *store.Query
|
||||
if packageID, ok := filters["package_id"].(uint); ok && packageID > 0 {
|
||||
query = query.Where("package_id = ?", packageID)
|
||||
}
|
||||
if allocationID, ok := filters["allocation_id"].(uint); ok && allocationID > 0 {
|
||||
query = query.Where("allocation_id = ?", allocationID)
|
||||
if seriesAllocationID, ok := filters["series_allocation_id"].(uint); ok && seriesAllocationID > 0 {
|
||||
query = query.Where("series_allocation_id = ?", seriesAllocationID)
|
||||
}
|
||||
if allocatorShopID, ok := filters["allocator_shop_id"].(uint); ok {
|
||||
query = query.Where("allocator_shop_id = ?", allocatorShopID)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok && status > 0 {
|
||||
query = query.Where("status = ?", status)
|
||||
@@ -102,8 +105,33 @@ func (s *ShopPackageAllocationStore) GetByShopID(ctx context.Context, shopID uin
|
||||
return allocations, nil
|
||||
}
|
||||
|
||||
func (s *ShopPackageAllocationStore) DeleteByAllocationID(ctx context.Context, allocationID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Delete(&model.ShopPackageAllocation{}).Error
|
||||
func (s *ShopPackageAllocationStore) GetByShopAndPackages(ctx context.Context, shopID uint, packageIDs []uint) ([]*model.ShopPackageAllocation, error) {
|
||||
var allocations []*model.ShopPackageAllocation
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ? AND package_id IN ? AND status = 1", shopID, packageIDs).
|
||||
Find(&allocations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allocations, nil
|
||||
}
|
||||
|
||||
func (s *ShopPackageAllocationStore) GetBySeriesAllocationID(ctx context.Context, seriesAllocationID uint) ([]*model.ShopPackageAllocation, error) {
|
||||
var allocations []*model.ShopPackageAllocation
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("series_allocation_id = ? AND status = 1", seriesAllocationID).
|
||||
Find(&allocations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allocations, nil
|
||||
}
|
||||
|
||||
func (s *ShopPackageAllocationStore) CountBySeriesAllocationID(ctx context.Context, seriesAllocationID uint) (int64, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.ShopPackageAllocation{}).
|
||||
Where("series_allocation_id = ?", seriesAllocationID).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ func TestShopPackageAllocationStore_Create(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 1,
|
||||
PackageID: 1,
|
||||
AllocationID: 1,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
ShopID: 1,
|
||||
PackageID: 1,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, allocation)
|
||||
@@ -36,11 +36,11 @@ func TestShopPackageAllocationStore_GetByID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 2,
|
||||
PackageID: 2,
|
||||
AllocationID: 1,
|
||||
CostPrice: 6000,
|
||||
Status: constants.StatusEnabled,
|
||||
ShopID: 2,
|
||||
PackageID: 2,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 6000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
@@ -64,11 +64,11 @@ func TestShopPackageAllocationStore_GetByShopAndPackage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 3,
|
||||
PackageID: 3,
|
||||
AllocationID: 1,
|
||||
CostPrice: 7000,
|
||||
Status: constants.StatusEnabled,
|
||||
ShopID: 3,
|
||||
PackageID: 3,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 7000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
@@ -92,11 +92,11 @@ func TestShopPackageAllocationStore_Update(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 4,
|
||||
PackageID: 4,
|
||||
AllocationID: 1,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
ShopID: 4,
|
||||
PackageID: 4,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
@@ -115,11 +115,11 @@ func TestShopPackageAllocationStore_Delete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 5,
|
||||
PackageID: 5,
|
||||
AllocationID: 1,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
ShopID: 5,
|
||||
PackageID: 5,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
@@ -136,9 +136,9 @@ func TestShopPackageAllocationStore_List(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
allocations := []*model.ShopPackageAllocation{
|
||||
{ShopID: 10, PackageID: 10, AllocationID: 1, CostPrice: 5000, Status: constants.StatusEnabled},
|
||||
{ShopID: 11, PackageID: 11, AllocationID: 1, CostPrice: 6000, Status: constants.StatusEnabled},
|
||||
{ShopID: 12, PackageID: 12, AllocationID: 2, CostPrice: 7000, Status: constants.StatusEnabled},
|
||||
{ShopID: 10, PackageID: 10, AllocatorShopID: 0, CostPrice: 5000, Status: constants.StatusEnabled},
|
||||
{ShopID: 11, PackageID: 11, AllocatorShopID: 0, CostPrice: 6000, Status: constants.StatusEnabled},
|
||||
{ShopID: 12, PackageID: 12, AllocatorShopID: 10, CostPrice: 7000, Status: constants.StatusEnabled},
|
||||
}
|
||||
for _, a := range allocations {
|
||||
require.NoError(t, s.Create(ctx, a))
|
||||
@@ -173,16 +173,6 @@ func TestShopPackageAllocationStore_List(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("按分配ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"allocation_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(2))
|
||||
for _, a := range result {
|
||||
assert.Equal(t, uint(1), a.AllocationID)
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
@@ -223,11 +213,11 @@ func TestShopPackageAllocationStore_UpdateStatus(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
allocation := &model.ShopPackageAllocation{
|
||||
ShopID: 20,
|
||||
PackageID: 20,
|
||||
AllocationID: 1,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
ShopID: 20,
|
||||
PackageID: 20,
|
||||
AllocatorShopID: 0,
|
||||
CostPrice: 5000,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -30,7 +30,9 @@ func (s *ShopSeriesAllocationStore) GetByID(ctx context.Context, id uint) (*mode
|
||||
|
||||
func (s *ShopSeriesAllocationStore) GetByShopAndSeries(ctx context.Context, shopID, seriesID uint) (*model.ShopSeriesAllocation, error) {
|
||||
var allocation model.ShopSeriesAllocation
|
||||
if err := s.db.WithContext(ctx).Where("shop_id = ? AND series_id = ?", shopID, seriesID).First(&allocation).Error; err != nil {
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ? AND series_id = ?", shopID, seriesID).
|
||||
First(&allocation).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &allocation, nil
|
||||
@@ -56,7 +58,7 @@ func (s *ShopSeriesAllocationStore) List(ctx context.Context, opts *store.QueryO
|
||||
if seriesID, ok := filters["series_id"].(uint); ok && seriesID > 0 {
|
||||
query = query.Where("series_id = ?", seriesID)
|
||||
}
|
||||
if allocatorShopID, ok := filters["allocator_shop_id"].(uint); ok && allocatorShopID > 0 {
|
||||
if allocatorShopID, ok := filters["allocator_shop_id"].(uint); ok {
|
||||
query = query.Where("allocator_shop_id = ?", allocatorShopID)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok && status > 0 {
|
||||
@@ -75,6 +77,8 @@ func (s *ShopSeriesAllocationStore) List(ctx context.Context, opts *store.QueryO
|
||||
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
} else {
|
||||
query = query.Order("id DESC")
|
||||
}
|
||||
|
||||
if err := query.Find(&allocations).Error; err != nil {
|
||||
@@ -94,31 +98,58 @@ func (s *ShopSeriesAllocationStore) UpdateStatus(ctx context.Context, id uint, s
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationStore) HasDependentAllocations(ctx context.Context, allocatorShopID, seriesID uint) (bool, error) {
|
||||
func (s *ShopSeriesAllocationStore) GetByShopID(ctx context.Context, shopID uint) ([]*model.ShopSeriesAllocation, error) {
|
||||
var allocations []*model.ShopSeriesAllocation
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ? AND status = 1", shopID).
|
||||
Find(&allocations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allocations, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationStore) CountBySeriesID(ctx context.Context, seriesID uint) (int64, error) {
|
||||
var count int64
|
||||
err := s.db.WithContext(ctx).
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.ShopSeriesAllocation{}).
|
||||
Where("allocator_shop_id IN (SELECT id FROM tb_shop WHERE parent_id = ?)", allocatorShopID).
|
||||
Where("series_id = ?", seriesID).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationStore) ExistsByShopAndSeries(ctx context.Context, shopID, seriesID uint) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.ShopSeriesAllocation{}).
|
||||
Where("shop_id = ? AND series_id = ?", shopID, seriesID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationStore) GetByShopID(ctx context.Context, shopID uint) ([]*model.ShopSeriesAllocation, error) {
|
||||
func (s *ShopSeriesAllocationStore) GetByAllocatorShopID(ctx context.Context, allocatorShopID uint) ([]*model.ShopSeriesAllocation, error) {
|
||||
var allocations []*model.ShopSeriesAllocation
|
||||
if err := s.db.WithContext(ctx).Where("shop_id = ? AND status = 1", shopID).Find(&allocations).Error; err != nil {
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("allocator_shop_id = ? AND status = 1", allocatorShopID).
|
||||
Find(&allocations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allocations, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesAllocationStore) GetByAllocatorShopID(ctx context.Context, allocatorShopID uint) ([]*model.ShopSeriesAllocation, error) {
|
||||
var allocations []*model.ShopSeriesAllocation
|
||||
if err := s.db.WithContext(ctx).Where("allocator_shop_id = ?", allocatorShopID).Find(&allocations).Error; err != nil {
|
||||
func (s *ShopSeriesAllocationStore) GetIDsByShopIDsAndSeries(ctx context.Context, shopIDs []uint, seriesID uint) ([]uint, error) {
|
||||
if len(shopIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var ids []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.ShopSeriesAllocation{}).
|
||||
Where("shop_id IN ? AND series_id = ? AND status = 1", shopIDs, seriesID).
|
||||
Pluck("id", &ids).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return allocations, nil
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestShopSeriesAllocationStore_GetByShopAndSeries(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
ctx := context.Background()
|
||||
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
|
||||
// 创建测试数据
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 1,
|
||||
SeriesID: 100,
|
||||
AllocatorShopID: 0,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
t.Run("查询存在的分配", func(t *testing.T) {
|
||||
result, err := s.GetByShopAndSeries(ctx, 1, 100)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, uint(1), result.ShopID)
|
||||
assert.Equal(t, uint(100), result.SeriesID)
|
||||
})
|
||||
|
||||
t.Run("查询不存在的分配", func(t *testing.T) {
|
||||
result, err := s.GetByShopAndSeries(ctx, 999, 999)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, gorm.ErrRecordNotFound, err)
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
ctx := context.Background()
|
||||
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 1,
|
||||
SeriesID: 100,
|
||||
AllocatorShopID: 0,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
err := s.Create(ctx, allocation)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, allocation.ID)
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
ctx := context.Background()
|
||||
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
|
||||
allocation := &model.ShopSeriesAllocation{
|
||||
ShopID: 1,
|
||||
SeriesID: 100,
|
||||
AllocatorShopID: 0,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, s.Create(ctx, allocation))
|
||||
|
||||
result, err := s.GetByID(ctx, allocation.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, allocation.ID, result.ID)
|
||||
}
|
||||
|
||||
func TestShopSeriesAllocationStore_List(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
ctx := context.Background()
|
||||
|
||||
s := NewShopSeriesAllocationStore(tx)
|
||||
|
||||
// 创建测试数据
|
||||
allocations := []*model.ShopSeriesAllocation{
|
||||
{ShopID: 1, SeriesID: 100, AllocatorShopID: 0, Status: 1},
|
||||
{ShopID: 1, SeriesID: 101, AllocatorShopID: 0, Status: 1},
|
||||
{ShopID: 2, SeriesID: 100, AllocatorShopID: 0, Status: 1},
|
||||
}
|
||||
for _, a := range allocations {
|
||||
require.NoError(t, s.Create(ctx, a))
|
||||
}
|
||||
|
||||
t.Run("按店铺ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"shop_id": uint(1)}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
|
||||
t.Run("按系列ID过滤", func(t *testing.T) {
|
||||
filters := map[string]interface{}{"series_id": uint(100)}
|
||||
result, total, err := s.List(ctx, nil, filters)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
}
|
||||
@@ -68,3 +68,29 @@ func (s *ShopSeriesCommissionStatsStore) ListExpired(ctx context.Context, before
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesCommissionStatsStore) GetAggregatedStats(ctx context.Context, allocationIDs []uint, periodType string, now time.Time) (int64, int64, error) {
|
||||
if len(allocationIDs) == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
var result struct {
|
||||
TotalSalesCount int64
|
||||
TotalSalesAmount int64
|
||||
}
|
||||
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&model.ShopSeriesCommissionStats{}).
|
||||
Select("COALESCE(SUM(total_sales_count), 0) as total_sales_count, COALESCE(SUM(total_sales_amount), 0) as total_sales_amount").
|
||||
Where("allocation_id IN ?", allocationIDs).
|
||||
Where("period_type = ?", periodType).
|
||||
Where("period_start <= ? AND period_end >= ?", now, now).
|
||||
Where("status = ?", model.StatsStatusActive).
|
||||
Scan(&result).Error
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return result.TotalSalesCount, result.TotalSalesAmount, nil
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ShopSeriesOneTimeCommissionTierStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewShopSeriesOneTimeCommissionTierStore(db *gorm.DB) *ShopSeriesOneTimeCommissionTierStore {
|
||||
return &ShopSeriesOneTimeCommissionTierStore{db: db}
|
||||
}
|
||||
|
||||
func (s *ShopSeriesOneTimeCommissionTierStore) Create(ctx context.Context, tier *model.ShopSeriesOneTimeCommissionTier) error {
|
||||
return s.db.WithContext(ctx).Create(tier).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesOneTimeCommissionTierStore) BatchCreate(ctx context.Context, tiers []*model.ShopSeriesOneTimeCommissionTier) error {
|
||||
if len(tiers) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.WithContext(ctx).Create(&tiers).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesOneTimeCommissionTierStore) GetByID(ctx context.Context, id uint) (*model.ShopSeriesOneTimeCommissionTier, error) {
|
||||
var tier model.ShopSeriesOneTimeCommissionTier
|
||||
if err := s.db.WithContext(ctx).First(&tier, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tier, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesOneTimeCommissionTierStore) Update(ctx context.Context, tier *model.ShopSeriesOneTimeCommissionTier) error {
|
||||
return s.db.WithContext(ctx).Save(tier).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesOneTimeCommissionTierStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.ShopSeriesOneTimeCommissionTier{}, id).Error
|
||||
}
|
||||
|
||||
func (s *ShopSeriesOneTimeCommissionTierStore) ListByAllocationID(ctx context.Context, allocationID uint) ([]*model.ShopSeriesOneTimeCommissionTier, error) {
|
||||
var tiers []*model.ShopSeriesOneTimeCommissionTier
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Where("status = ?", 1).
|
||||
Order("threshold_value ASC").
|
||||
Find(&tiers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tiers, nil
|
||||
}
|
||||
|
||||
func (s *ShopSeriesOneTimeCommissionTierStore) DeleteByAllocationID(ctx context.Context, allocationID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("allocation_id = ?", allocationID).
|
||||
Delete(&model.ShopSeriesOneTimeCommissionTier{}).Error
|
||||
}
|
||||
Reference in New Issue
Block a user