From c7bf43f306c1a841db585ef5523771fabf6f445c Mon Sep 17 00:00:00 2001 From: huang Date: Sat, 31 Jan 2026 11:46:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(commission):=20=E4=BB=A3=E8=B4=AD=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E8=B7=B3=E8=BF=87=E4=B8=80=E6=AC=A1=E6=80=A7=E4=BD=A3?= =?UTF-8?q?=E9=87=91=E5=92=8C=E7=B4=AF=E8=AE=A1=E5=85=85=E5=80=BC=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/commission_calculation/service.go | 27 +- .../commission_calculation/service_test.go | 369 ++++++++++++++++++ 2 files changed, 389 insertions(+), 7 deletions(-) create mode 100644 internal/service/commission_calculation/service_test.go diff --git a/internal/service/commission_calculation/service.go b/internal/service/commission_calculation/service.go index d5ce4f8..7423aaa 100644 --- a/internal/service/commission_calculation/service.go +++ b/internal/service/commission_calculation/service.go @@ -91,13 +91,16 @@ func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error { } } - if order.OrderType == model.OrderTypeSingleCard && order.IotCardID != nil { - if err := s.triggerOneTimeCommissionForCardInTx(ctx, tx, order, *order.IotCardID); err != nil { - return errors.Wrap(errors.CodeInternalError, err, "触发单卡一次性佣金失败") - } - } else if order.OrderType == model.OrderTypeDevice && order.DeviceID != nil { - if err := s.triggerOneTimeCommissionForDeviceInTx(ctx, tx, order, *order.DeviceID); err != nil { - return errors.Wrap(errors.CodeInternalError, err, "触发设备一次性佣金失败") + // 代购订单不触发一次性佣金和累计充值更新 + if !order.IsPurchaseOnBehalf { + if order.OrderType == model.OrderTypeSingleCard && order.IotCardID != nil { + if err := s.triggerOneTimeCommissionForCardInTx(ctx, tx, order, *order.IotCardID); err != nil { + return errors.Wrap(errors.CodeInternalError, err, "触发单卡一次性佣金失败") + } + } else if order.OrderType == model.OrderTypeDevice && order.DeviceID != nil { + if err := s.triggerOneTimeCommissionForDeviceInTx(ctx, tx, order, *order.DeviceID); err != nil { + return errors.Wrap(errors.CodeInternalError, err, "触发设备一次性佣金失败") + } } } @@ -189,6 +192,11 @@ func (s *Service) calculateCostPrice(allocation *model.ShopSeriesAllocation, ord } func (s *Service) triggerOneTimeCommissionForCardInTx(ctx context.Context, tx *gorm.DB, order *model.Order, cardID uint) error { + // 代购订单不触发一次性佣金和累计充值更新 + if order.IsPurchaseOnBehalf { + return nil + } + card, err := s.iotCardStore.GetByID(ctx, cardID) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取卡信息失败") @@ -284,6 +292,11 @@ func (s *Service) TriggerOneTimeCommissionForCard(ctx context.Context, order *mo } func (s *Service) triggerOneTimeCommissionForDeviceInTx(ctx context.Context, tx *gorm.DB, order *model.Order, deviceID uint) error { + // 代购订单不触发一次性佣金和累计充值更新 + if order.IsPurchaseOnBehalf { + return nil + } + device, err := s.deviceStore.GetByID(ctx, deviceID) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取设备信息失败") diff --git a/internal/service/commission_calculation/service_test.go b/internal/service/commission_calculation/service_test.go new file mode 100644 index 0000000..3bf8608 --- /dev/null +++ b/internal/service/commission_calculation/service_test.go @@ -0,0 +1,369 @@ +package commission_calculation + +import ( + "context" + "testing" + "time" + + "github.com/break/junhong_cmp_fiber/internal/model" + "github.com/break/junhong_cmp_fiber/internal/service/commission_stats" + "github.com/break/junhong_cmp_fiber/internal/store/postgres" + "github.com/break/junhong_cmp_fiber/tests/testutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func TestCalculateCommission_PurchaseOnBehalf(t *testing.T) { + tx := testutils.NewTestTransaction(t) + rdb := testutils.GetTestRedis(t) + testutils.CleanTestRedisKeys(t, rdb) + + commissionRecordStore := postgres.NewCommissionRecordStore(tx, rdb) + shopStore := postgres.NewShopStore(tx, rdb) + shopSeriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) + shopSeriesOneTimeCommissionTierStore := postgres.NewShopSeriesOneTimeCommissionTierStore(tx) + iotCardStore := postgres.NewIotCardStore(tx, rdb) + deviceStore := postgres.NewDeviceStore(tx, rdb) + walletStore := postgres.NewWalletStore(tx, rdb) + walletTransactionStore := postgres.NewWalletTransactionStore(tx, rdb) + orderStore := postgres.NewOrderStore(tx, rdb) + orderItemStore := postgres.NewOrderItemStore(tx, rdb) + packageStore := postgres.NewPackageStore(tx) + statsStore := postgres.NewShopSeriesCommissionStatsStore(tx) + commissionStatsService := commission_stats.New(statsStore) + + service := New( + tx, + commissionRecordStore, + shopStore, + shopSeriesAllocationStore, + shopSeriesOneTimeCommissionTierStore, + iotCardStore, + deviceStore, + walletStore, + walletTransactionStore, + orderStore, + orderItemStore, + packageStore, + commissionStatsService, + zap.NewNop(), + ) + + ctx := context.Background() + + shop := &model.Shop{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + ShopName: "测试店铺", + ShopCode: "TEST001", + ContactName: "测试联系人", + ContactPhone: "13800000001", + } + require.NoError(t, tx.Create(shop).Error) + + wallet := &model.Wallet{ + ResourceType: "shop", + ResourceID: shop.ID, + WalletType: "commission", + Balance: 0, + Version: 1, + BaseModel: model.BaseModel{Creator: 1, Updater: 1}, + } + require.NoError(t, tx.Create(wallet).Error) + + allocation := &model.ShopSeriesAllocation{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + ShopID: shop.ID, + SeriesID: 1, + AllocatorShopID: 1, + BaseCommissionMode: model.CommissionModeFixed, + BaseCommissionValue: 5000, + EnableOneTimeCommission: true, + OneTimeCommissionTrigger: model.OneTimeCommissionTriggerAccumulatedRecharge, + OneTimeCommissionThreshold: 10000, + OneTimeCommissionType: model.OneTimeCommissionTypeFixed, + OneTimeCommissionMode: model.CommissionModeFixed, + OneTimeCommissionValue: 1000, + Status: 1, + } + require.NoError(t, tx.Create(allocation).Error) + + card := &model.IotCard{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + ICCID: "89860000000000000001", + ShopID: &shop.ID, + SeriesAllocationID: &allocation.ID, + AccumulatedRecharge: 0, + FirstCommissionPaid: false, + } + require.NoError(t, tx.Create(card).Error) + + seriesID := allocation.SeriesID + + tests := []struct { + name string + isPurchaseOnBehalf bool + expectedAccumulatedRecharge int64 + expectedCommissionRecords int + expectedOneTimeCommission bool + }{ + { + name: "普通订单_触发累计充值和一次性佣金", + isPurchaseOnBehalf: false, + expectedAccumulatedRecharge: 15000, + expectedCommissionRecords: 2, + expectedOneTimeCommission: true, + }, + { + name: "代购订单_不触发累计充值和一次性佣金", + isPurchaseOnBehalf: true, + expectedAccumulatedRecharge: 0, + expectedCommissionRecords: 1, + expectedOneTimeCommission: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.NoError(t, tx.Model(&model.IotCard{}).Where("id = ?", card.ID).Updates(map[string]interface{}{ + "accumulated_recharge": 0, + "first_commission_paid": false, + }).Error) + + require.NoError(t, tx.Where("1=1").Delete(&model.CommissionRecord{}).Error) + require.NoError(t, tx.Where("1=1").Delete(&model.Order{}).Error) + + order := &model.Order{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + OrderNo: "ORD" + time.Now().Format("20060102150405"), + OrderType: model.OrderTypeSingleCard, + IotCardID: &card.ID, + BuyerType: model.BuyerTypeAgent, + BuyerID: shop.ID, + SellerShopID: &shop.ID, + SeriesID: &seriesID, + TotalAmount: 15000, + SellerCostPrice: 5000, + IsPurchaseOnBehalf: tt.isPurchaseOnBehalf, + CommissionStatus: model.CommissionStatusPending, + PaymentStatus: model.PaymentStatusPaid, + } + require.NoError(t, tx.Create(order).Error) + + err := service.CalculateCommission(ctx, order.ID) + require.NoError(t, err) + + var updatedCard model.IotCard + require.NoError(t, tx.First(&updatedCard, card.ID).Error) + assert.Equal(t, tt.expectedAccumulatedRecharge, updatedCard.AccumulatedRecharge, "累计充值金额不符合预期") + + var records []model.CommissionRecord + require.NoError(t, tx.Where("order_id = ?", order.ID).Find(&records).Error) + assert.Equal(t, tt.expectedCommissionRecords, len(records), "佣金记录数量不符合预期") + + hasOneTimeCommission := false + for _, record := range records { + if record.CommissionSource == model.CommissionSourceOneTime { + hasOneTimeCommission = true + break + } + } + assert.Equal(t, tt.expectedOneTimeCommission, hasOneTimeCommission, "一次性佣金触发状态不符合预期") + + if tt.expectedOneTimeCommission { + assert.True(t, updatedCard.FirstCommissionPaid, "首次佣金发放标记应为true") + } else { + assert.False(t, updatedCard.FirstCommissionPaid, "首次佣金发放标记应为false") + } + }) + } +} + +func TestCalculateCommission_Device_PurchaseOnBehalf(t *testing.T) { + tx := testutils.NewTestTransaction(t) + rdb := testutils.GetTestRedis(t) + testutils.CleanTestRedisKeys(t, rdb) + + commissionRecordStore := postgres.NewCommissionRecordStore(tx, rdb) + shopStore := postgres.NewShopStore(tx, rdb) + shopSeriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx) + shopSeriesOneTimeCommissionTierStore := postgres.NewShopSeriesOneTimeCommissionTierStore(tx) + iotCardStore := postgres.NewIotCardStore(tx, rdb) + deviceStore := postgres.NewDeviceStore(tx, rdb) + walletStore := postgres.NewWalletStore(tx, rdb) + walletTransactionStore := postgres.NewWalletTransactionStore(tx, rdb) + orderStore := postgres.NewOrderStore(tx, rdb) + orderItemStore := postgres.NewOrderItemStore(tx, rdb) + packageStore := postgres.NewPackageStore(tx) + statsStore := postgres.NewShopSeriesCommissionStatsStore(tx) + commissionStatsService := commission_stats.New(statsStore) + + service := New( + tx, + commissionRecordStore, + shopStore, + shopSeriesAllocationStore, + shopSeriesOneTimeCommissionTierStore, + iotCardStore, + deviceStore, + walletStore, + walletTransactionStore, + orderStore, + orderItemStore, + packageStore, + commissionStatsService, + zap.NewNop(), + ) + + ctx := context.Background() + + shop := &model.Shop{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + ShopName: "测试店铺", + ShopCode: "TEST002", + ContactName: "测试联系人", + ContactPhone: "13800000002", + } + require.NoError(t, tx.Create(shop).Error) + + wallet := &model.Wallet{ + ResourceType: "shop", + ResourceID: shop.ID, + WalletType: "commission", + Balance: 0, + Version: 1, + BaseModel: model.BaseModel{Creator: 1, Updater: 1}, + } + require.NoError(t, tx.Create(wallet).Error) + + allocation := &model.ShopSeriesAllocation{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + ShopID: shop.ID, + SeriesID: 1, + AllocatorShopID: 1, + BaseCommissionMode: model.CommissionModeFixed, + BaseCommissionValue: 5000, + EnableOneTimeCommission: true, + OneTimeCommissionTrigger: model.OneTimeCommissionTriggerAccumulatedRecharge, + OneTimeCommissionThreshold: 10000, + OneTimeCommissionType: model.OneTimeCommissionTypeFixed, + OneTimeCommissionMode: model.CommissionModeFixed, + OneTimeCommissionValue: 1000, + Status: 1, + } + require.NoError(t, tx.Create(allocation).Error) + + device := &model.Device{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + DeviceNo: "DEV001", + ShopID: &shop.ID, + SeriesAllocationID: &allocation.ID, + AccumulatedRecharge: 0, + FirstCommissionPaid: false, + } + require.NoError(t, tx.Create(device).Error) + + seriesID := allocation.SeriesID + + tests := []struct { + name string + isPurchaseOnBehalf bool + expectedAccumulatedRecharge int64 + expectedCommissionRecords int + expectedOneTimeCommission bool + }{ + { + name: "普通订单_触发累计充值和一次性佣金", + isPurchaseOnBehalf: false, + expectedAccumulatedRecharge: 15000, + expectedCommissionRecords: 2, + expectedOneTimeCommission: true, + }, + { + name: "代购订单_不触发累计充值和一次性佣金", + isPurchaseOnBehalf: true, + expectedAccumulatedRecharge: 0, + expectedCommissionRecords: 1, + expectedOneTimeCommission: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.NoError(t, tx.Model(&model.Device{}).Where("id = ?", device.ID).Updates(map[string]interface{}{ + "accumulated_recharge": 0, + "first_commission_paid": false, + }).Error) + + require.NoError(t, tx.Where("1=1").Delete(&model.CommissionRecord{}).Error) + require.NoError(t, tx.Where("1=1").Delete(&model.Order{}).Error) + + order := &model.Order{ + BaseModel: model.BaseModel{ + Creator: 1, + Updater: 1, + }, + OrderNo: "ORD" + time.Now().Format("20060102150405"), + OrderType: model.OrderTypeDevice, + DeviceID: &device.ID, + BuyerType: model.BuyerTypeAgent, + BuyerID: shop.ID, + SellerShopID: &shop.ID, + SeriesID: &seriesID, + TotalAmount: 15000, + SellerCostPrice: 5000, + IsPurchaseOnBehalf: tt.isPurchaseOnBehalf, + CommissionStatus: model.CommissionStatusPending, + PaymentStatus: model.PaymentStatusPaid, + } + require.NoError(t, tx.Create(order).Error) + + err := service.CalculateCommission(ctx, order.ID) + require.NoError(t, err) + + var updatedDevice model.Device + require.NoError(t, tx.First(&updatedDevice, device.ID).Error) + assert.Equal(t, tt.expectedAccumulatedRecharge, updatedDevice.AccumulatedRecharge, "累计充值金额不符合预期") + + var records []model.CommissionRecord + require.NoError(t, tx.Where("order_id = ?", order.ID).Find(&records).Error) + assert.Equal(t, tt.expectedCommissionRecords, len(records), "佣金记录数量不符合预期") + + hasOneTimeCommission := false + for _, record := range records { + if record.CommissionSource == model.CommissionSourceOneTime { + hasOneTimeCommission = true + break + } + } + assert.Equal(t, tt.expectedOneTimeCommission, hasOneTimeCommission, "一次性佣金触发状态不符合预期") + + if tt.expectedOneTimeCommission { + assert.True(t, updatedDevice.FirstCommissionPaid, "首次佣金发放标记应为true") + } else { + assert.False(t, updatedDevice.FirstCommissionPaid, "首次佣金发放标记应为false") + } + }) + } +}