package commission_calculation import ( "context" "fmt" "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/pkg/errors" "go.uber.org/zap" "gorm.io/gorm" ) type Service struct { db *gorm.DB commissionRecordStore *postgres.CommissionRecordStore shopStore *postgres.ShopStore shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore shopSeriesOneTimeCommissionTierStore *postgres.ShopSeriesOneTimeCommissionTierStore iotCardStore *postgres.IotCardStore deviceStore *postgres.DeviceStore walletStore *postgres.WalletStore walletTransactionStore *postgres.WalletTransactionStore orderStore *postgres.OrderStore orderItemStore *postgres.OrderItemStore packageStore *postgres.PackageStore commissionStatsService *commission_stats.Service logger *zap.Logger } func New( db *gorm.DB, commissionRecordStore *postgres.CommissionRecordStore, shopStore *postgres.ShopStore, shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore, shopSeriesOneTimeCommissionTierStore *postgres.ShopSeriesOneTimeCommissionTierStore, iotCardStore *postgres.IotCardStore, deviceStore *postgres.DeviceStore, walletStore *postgres.WalletStore, walletTransactionStore *postgres.WalletTransactionStore, orderStore *postgres.OrderStore, orderItemStore *postgres.OrderItemStore, packageStore *postgres.PackageStore, commissionStatsService *commission_stats.Service, logger *zap.Logger, ) *Service { return &Service{ db: db, commissionRecordStore: commissionRecordStore, shopStore: shopStore, shopSeriesAllocationStore: shopSeriesAllocationStore, shopSeriesOneTimeCommissionTierStore: shopSeriesOneTimeCommissionTierStore, iotCardStore: iotCardStore, deviceStore: deviceStore, walletStore: walletStore, walletTransactionStore: walletTransactionStore, orderStore: orderStore, orderItemStore: orderItemStore, packageStore: packageStore, commissionStatsService: commissionStatsService, logger: logger, } } func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error { return s.db.Transaction(func(tx *gorm.DB) error { order, err := s.orderStore.GetByID(ctx, orderID) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取订单失败") } if order.CommissionStatus == model.CommissionStatusCalculated { s.logger.Warn("订单佣金已计算,跳过", zap.String("order_id", fmt.Sprint(orderID))) return nil } costDiffRecords, err := s.CalculateCostDiffCommission(ctx, order) if err != nil { return errors.Wrap(errors.CodeInternalError, err, "计算成本价差佣金失败") } for _, record := range costDiffRecords { if err := s.commissionRecordStore.Create(ctx, record); err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建成本价差佣金记录失败") } if err := s.CreditCommission(ctx, record); err != nil { return errors.Wrap(errors.CodeInternalError, err, "佣金入账失败") } } if order.OrderType == model.OrderTypeSingleCard && order.IotCardID != nil { if err := s.TriggerOneTimeCommissionForCard(ctx, order, *order.IotCardID); err != nil { return errors.Wrap(errors.CodeInternalError, err, "触发单卡一次性佣金失败") } } else if order.OrderType == model.OrderTypeDevice && order.DeviceID != nil { if err := s.TriggerOneTimeCommissionForDevice(ctx, order, *order.DeviceID); err != nil { return errors.Wrap(errors.CodeInternalError, err, "触发设备一次性佣金失败") } } order.CommissionStatus = model.CommissionStatusCalculated if err := s.orderStore.Update(ctx, order); err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "更新订单佣金状态失败") } return nil }) } func (s *Service) CalculateCostDiffCommission(ctx context.Context, order *model.Order) ([]*model.CommissionRecord, error) { var records []*model.CommissionRecord if order.SellerShopID == nil { return records, nil } sellerShop, err := s.shopStore.GetByID(ctx, *order.SellerShopID) if err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "获取销售店铺失败") } sellerProfit := order.TotalAmount - order.SellerCostPrice if sellerProfit > 0 { records = append(records, &model.CommissionRecord{ BaseModel: model.BaseModel{ Creator: order.Creator, Updater: order.Updater, }, ShopID: *order.SellerShopID, OrderID: order.ID, IotCardID: order.IotCardID, DeviceID: order.DeviceID, CommissionSource: model.CommissionSourceCostDiff, Amount: sellerProfit, Status: model.CommissionStatusReleased, }) } childCostPrice := order.SellerCostPrice currentShopID := sellerShop.ParentID for currentShopID != nil && *currentShopID > 0 { currentShop, err := s.shopStore.GetByID(ctx, *currentShopID) if err != nil { s.logger.Error("获取上级店铺失败", zap.Uint("shop_id", *currentShopID), zap.Error(err)) break } allocation, err := s.shopSeriesAllocationStore.GetByShopAndSeries(ctx, currentShop.ID, *order.SeriesID) if err != nil { s.logger.Warn("上级店铺未分配该系列,跳过", zap.Uint("shop_id", currentShop.ID), zap.Uint("series_id", *order.SeriesID)) break } myCostPrice := s.calculateCostPrice(allocation, order.TotalAmount) profit := childCostPrice - myCostPrice if profit > 0 { records = append(records, &model.CommissionRecord{ BaseModel: model.BaseModel{ Creator: order.Creator, Updater: order.Updater, }, ShopID: currentShop.ID, OrderID: order.ID, IotCardID: order.IotCardID, DeviceID: order.DeviceID, CommissionSource: model.CommissionSourceCostDiff, Amount: profit, Status: model.CommissionStatusReleased, }) } childCostPrice = myCostPrice currentShopID = currentShop.ParentID } return records, nil } func (s *Service) calculateCostPrice(allocation *model.ShopSeriesAllocation, orderAmount int64) int64 { if allocation.BaseCommissionMode == model.CommissionModeFixed { return orderAmount - allocation.BaseCommissionValue } else if allocation.BaseCommissionMode == model.CommissionModePercent { commission := orderAmount * allocation.BaseCommissionValue / 1000 return orderAmount - commission } return orderAmount } func (s *Service) TriggerOneTimeCommissionForCard(ctx context.Context, order *model.Order, cardID uint) error { card, err := s.iotCardStore.GetByID(ctx, cardID) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取卡信息失败") } if card.FirstCommissionPaid { return nil } if card.SeriesAllocationID == nil { return nil } allocation, err := s.shopSeriesAllocationStore.GetByID(ctx, *card.SeriesAllocationID) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取系列分配失败") } if !allocation.EnableOneTimeCommission { return nil } var rechargeAmount int64 switch allocation.OneTimeCommissionTrigger { case model.OneTimeCommissionTriggerSingleRecharge: rechargeAmount = order.TotalAmount case model.OneTimeCommissionTriggerAccumulatedRecharge: rechargeAmount = card.AccumulatedRecharge + order.TotalAmount default: return nil } if rechargeAmount < allocation.OneTimeCommissionThreshold { return nil } commissionAmount, err := s.calculateOneTimeCommission(ctx, allocation, order.TotalAmount) if err != nil { return errors.Wrap(errors.CodeInternalError, err, "计算一次性佣金失败") } if commissionAmount <= 0 { return nil } if card.ShopID == nil { return errors.New(errors.CodeInvalidParam, "卡未归属任何店铺,无法发放佣金") } record := &model.CommissionRecord{ BaseModel: model.BaseModel{ Creator: order.Creator, Updater: order.Updater, }, ShopID: *card.ShopID, OrderID: order.ID, IotCardID: &cardID, CommissionSource: model.CommissionSourceOneTime, Amount: commissionAmount, Status: model.CommissionStatusReleased, Remark: "一次性佣金", } if err := s.commissionRecordStore.Create(ctx, record); err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建一次性佣金记录失败") } if err := s.CreditCommission(ctx, record); err != nil { return errors.Wrap(errors.CodeInternalError, err, "一次性佣金入账失败") } card.FirstCommissionPaid = true if allocation.OneTimeCommissionTrigger == model.OneTimeCommissionTriggerAccumulatedRecharge { card.AccumulatedRecharge = rechargeAmount } if err := s.iotCardStore.Update(ctx, card); err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "更新卡状态失败") } return nil } func (s *Service) TriggerOneTimeCommissionForDevice(ctx context.Context, order *model.Order, deviceID uint) error { device, err := s.deviceStore.GetByID(ctx, deviceID) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取设备信息失败") } if device.FirstCommissionPaid { return nil } if device.SeriesAllocationID == nil { return nil } allocation, err := s.shopSeriesAllocationStore.GetByID(ctx, *device.SeriesAllocationID) if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取系列分配失败") } if !allocation.EnableOneTimeCommission { return nil } var rechargeAmount int64 switch allocation.OneTimeCommissionTrigger { case model.OneTimeCommissionTriggerSingleRecharge: rechargeAmount = order.TotalAmount case model.OneTimeCommissionTriggerAccumulatedRecharge: rechargeAmount = device.AccumulatedRecharge + order.TotalAmount default: return nil } if rechargeAmount < allocation.OneTimeCommissionThreshold { return nil } commissionAmount, err := s.calculateOneTimeCommission(ctx, allocation, order.TotalAmount) if err != nil { return errors.Wrap(errors.CodeInternalError, err, "计算一次性佣金失败") } if commissionAmount <= 0 { return nil } if device.ShopID == nil { return errors.New(errors.CodeInvalidParam, "设备未归属任何店铺,无法发放佣金") } record := &model.CommissionRecord{ BaseModel: model.BaseModel{ Creator: order.Creator, Updater: order.Updater, }, ShopID: *device.ShopID, OrderID: order.ID, DeviceID: &deviceID, CommissionSource: model.CommissionSourceOneTime, Amount: commissionAmount, Status: model.CommissionStatusReleased, Remark: "一次性佣金(设备)", } if err := s.commissionRecordStore.Create(ctx, record); err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建一次性佣金记录失败") } if err := s.CreditCommission(ctx, record); err != nil { return errors.Wrap(errors.CodeInternalError, err, "一次性佣金入账失败") } device.FirstCommissionPaid = true if allocation.OneTimeCommissionTrigger == model.OneTimeCommissionTriggerAccumulatedRecharge { device.AccumulatedRecharge = rechargeAmount } if err := s.deviceStore.Update(ctx, device); err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "更新设备状态失败") } return nil } func (s *Service) calculateOneTimeCommission(ctx context.Context, allocation *model.ShopSeriesAllocation, orderAmount int64) (int64, error) { switch allocation.OneTimeCommissionType { case model.OneTimeCommissionTypeFixed: return s.calculateFixedCommission(allocation.OneTimeCommissionMode, allocation.OneTimeCommissionValue, orderAmount), nil case model.OneTimeCommissionTypeTiered: return s.calculateTieredCommission(ctx, allocation.ID, orderAmount) } return 0, nil } func (s *Service) calculateFixedCommission(mode string, value int64, orderAmount int64) int64 { if mode == model.CommissionModeFixed { return value } else if mode == model.CommissionModePercent { return orderAmount * value / 1000 } return 0 } func (s *Service) calculateTieredCommission(ctx context.Context, allocationID uint, orderAmount int64) (int64, error) { tiers, err := s.shopSeriesOneTimeCommissionTierStore.ListByAllocationID(ctx, allocationID) if err != nil { return 0, errors.Wrap(errors.CodeDatabaseError, err, "获取梯度配置失败") } if len(tiers) == 0 { return 0, nil } stats, err := s.commissionStatsService.GetCurrentStats(ctx, allocationID, "all_time") if err != nil { s.logger.Error("获取销售业绩统计失败", zap.Uint("allocation_id", allocationID), zap.Error(err)) return 0, nil } if stats == nil { return 0, nil } var matchedTier *model.ShopSeriesOneTimeCommissionTier for _, tier := range tiers { var salesValue int64 if tier.TierType == model.TierTypeSalesCount { salesValue = stats.TotalSalesCount } else if tier.TierType == model.TierTypeSalesAmount { salesValue = stats.TotalSalesAmount } else { continue } if salesValue >= tier.ThresholdValue { if matchedTier == nil || tier.ThresholdValue > matchedTier.ThresholdValue { matchedTier = tier } } } if matchedTier == nil { return 0, nil } if matchedTier.CommissionMode == model.CommissionModeFixed { return matchedTier.CommissionValue, nil } else if matchedTier.CommissionMode == model.CommissionModePercent { return orderAmount * matchedTier.CommissionValue / 1000, nil } return 0, nil } func (s *Service) CreditCommission(ctx context.Context, record *model.CommissionRecord) error { wallet, err := s.walletStore.GetByResourceTypeAndID(ctx, "shop", record.ShopID, "commission") if err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "获取店铺钱包失败") } wallet.Balance += record.Amount if err := s.db.WithContext(ctx).Model(wallet).Update("balance", wallet.Balance).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "更新钱包余额失败") } now := time.Now() record.BalanceAfter = wallet.Balance record.Status = model.CommissionStatusReleased record.ReleasedAt = &now if err := s.db.WithContext(ctx).Model(record).Updates(record).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "更新佣金记录失败") } remark := "佣金入账" transaction := &model.WalletTransaction{ WalletID: wallet.ID, UserID: record.Creator, TransactionType: "commission", Amount: record.Amount, BalanceBefore: wallet.Balance - record.Amount, BalanceAfter: wallet.Balance, Status: 1, ReferenceType: stringPtr("commission"), ReferenceID: &record.ID, Remark: &remark, Creator: record.Creator, } if err := s.walletTransactionStore.Create(ctx, transaction); err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建钱包交易记录失败") } return nil } func stringPtr(s string) *string { return &s }