package user import ( "context" "errors" "fmt" "time" "bindbox-game/internal/repository/mysql/model" "gorm.io/gorm" ) func (s *service) AddCoupon(ctx context.Context, userID int64, couponID int64) error { tpl, err := s.readDB.SystemCoupons.WithContext(ctx).Where(s.readDB.SystemCoupons.ID.Eq(couponID)).First() if err != nil { return err } if tpl == nil || tpl.Status != 1 { // 模板不存在或未启用 fmt.Printf("[发券] 模板不可用 coupon_id=%d status=%d\n", couponID, func() int32 { if tpl != nil { return tpl.Status } return -1 }()) return errors.New("coupon not found or disabled") } // 配额检查:若 TotalQuantity > 0 则限制发放总量 if tpl.TotalQuantity > 0 { issued, ierr := s.readDB.UserCoupons.WithContext(ctx).Where(s.readDB.UserCoupons.CouponID.Eq(couponID)).Count() if ierr != nil { return ierr } if issued >= tpl.TotalQuantity { fmt.Printf("[发券] 模板配额已满 coupon_id=%d issued=%d total=%d\n", couponID, issued, tpl.TotalQuantity) return gorm.ErrInvalidData } } // 允许用户重复持有相同模板的未使用优惠券 item := &model.UserCoupons{UserID: userID, CouponID: couponID, Status: 1} if !tpl.ValidStart.IsZero() { item.ValidStart = tpl.ValidStart } else { item.ValidStart = time.Now() } if !tpl.ValidEnd.IsZero() { item.ValidEnd = tpl.ValidEnd } do := s.writeDB.UserCoupons.WithContext(ctx).Omit(s.readDB.UserCoupons.UsedAt, s.readDB.UserCoupons.UsedOrderID) if tpl.ValidEnd.IsZero() { do = do.Omit(s.writeDB.UserCoupons.ValidEnd) } if err := do.Create(item); err != nil { return err } // 直减金额券初始化余额(分);其他类型余额置0 if tpl.DiscountType == 1 && tpl.DiscountValue > 0 { _, _ = s.writeDB.UserCoupons.WithContext(ctx).Where(s.writeDB.UserCoupons.ID.Eq(item.ID)).Updates(map[string]any{"balance_amount": tpl.DiscountValue}) } else { _, _ = s.writeDB.UserCoupons.WithContext(ctx).Where(s.writeDB.UserCoupons.ID.Eq(item.ID)).Updates(map[string]any{"balance_amount": 0}) } return nil } func (s *service) VoidUserCoupon(ctx context.Context, adminID int64, userID int64, userCouponID int64) error { if userID <= 0 || userCouponID <= 0 { return fmt.Errorf("invalid_params") } uc, err := s.readDB.UserCoupons.WithContext(ctx). Where(s.readDB.UserCoupons.ID.Eq(userCouponID)). Where(s.readDB.UserCoupons.UserID.Eq(userID)). First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("not_found") } return err } if uc.Status != 1 { return fmt.Errorf("invalid_status") } db := s.repo.GetDbW() if uc.BalanceAmount > 0 { nb := int64(0) if err := db.Exec("UPDATE user_coupons SET status=?, balance_amount=?, updated_at=NOW(3) WHERE id=? AND user_id=? AND status=1", 3, nb, userCouponID, userID).Error; err != nil { return err } ledger := &model.UserCouponLedger{ UserID: userID, UserCouponID: userCouponID, ChangeAmount: -uc.BalanceAmount, BalanceAfter: nb, OrderID: 0, Action: "void_by_admin", CreatedAt: time.Now(), } if err := db.Create(ledger).Error; err != nil { return err } return nil } if err := db.Exec("UPDATE user_coupons SET status=?, updated_at=NOW(3) WHERE id=? AND user_id=? AND status=1", 3, userCouponID, userID).Error; err != nil { return err } _ = adminID return nil }