package service import ( "context" "dianshang/internal/model" "dianshang/internal/repository" "errors" "fmt" "time" ) // CouponService 优惠券服务 type CouponService struct { couponRepo *repository.CouponRepository } // NewCouponService 创建优惠券服务 func NewCouponService(couponRepo *repository.CouponRepository) *CouponService { return &CouponService{ couponRepo: couponRepo, } } // GetAvailableCoupons 获取可用优惠券列表 func (s *CouponService) GetAvailableCoupons() ([]model.Coupon, error) { return s.couponRepo.GetAvailableCoupons() } // GetAvailableCouponsWithUserStatus 获取可用优惠券列表(包含用户已领取状态) func (s *CouponService) GetAvailableCouponsWithUserStatus(userID uint) ([]map[string]interface{}, error) { // 获取所有可用优惠券 coupons, err := s.couponRepo.GetAvailableCoupons() if err != nil { return nil, err } var result []map[string]interface{} // 如果用户已登录,检查每个优惠券的领取状态 for _, coupon := range coupons { couponData := map[string]interface{}{ "id": coupon.ID, "name": coupon.Name, "type": coupon.Type, "value": coupon.Value, "min_amount": coupon.MinAmount, "description": coupon.Description, "start_time": coupon.StartTime, "end_time": coupon.EndTime, "total_count": coupon.TotalCount, "used_count": coupon.UsedCount, "is_received": false, // 默认未领取 } // 如果用户已登录,检查是否已领取 if userID > 0 { exists, err := s.couponRepo.CheckUserCouponExists(userID, coupon.ID) if err == nil { couponData["is_received"] = exists } } result = append(result, couponData) } return result, nil } // GetUserCoupons 获取用户优惠券 func (s *CouponService) GetUserCoupons(userID uint, status int) ([]model.UserCoupon, error) { return s.couponRepo.GetUserCoupons(userID, status) } // ReceiveCoupon 领取优惠券 func (s *CouponService) ReceiveCoupon(userID, couponID uint) error { // 检查优惠券是否存在且有效 coupon, err := s.couponRepo.GetByID(couponID) if err != nil { return errors.New("优惠券不存在") } // 检查是否在有效期内 now := time.Now() if now.Before(coupon.StartTime) || now.After(coupon.EndTime) { return errors.New("优惠券不在有效期内") } // 检查是否还有库存 if coupon.TotalCount > 0 && coupon.UsedCount >= coupon.TotalCount { return errors.New("优惠券已被领完") } // 检查用户是否已经领取过 exists, err := s.couponRepo.CheckUserCouponExists(userID, couponID) if err != nil { return err } if exists { return errors.New("您已经领取过该优惠券") } // 创建用户优惠券记录 userCoupon := &model.UserCoupon{ UserID: userID, CouponID: couponID, Status: 0, // 未使用 } return s.couponRepo.CreateUserCoupon(userCoupon) } // UseCoupon 使用优惠券 func (s *CouponService) UseCoupon(userID, userCouponID, orderID uint) error { // 获取用户优惠券 userCoupon, err := s.couponRepo.GetUserCouponByID(userCouponID) if err != nil { return errors.New("优惠券不存在") } // 检查是否属于该用户 if userCoupon.UserID != userID { return errors.New("无权使用该优惠券") } // 检查是否已使用 if userCoupon.Status != 0 { return errors.New("优惠券已使用或已过期") } // 检查优惠券是否在有效期内 now := time.Now() if now.Before(userCoupon.Coupon.StartTime) || now.After(userCoupon.Coupon.EndTime) { return errors.New("优惠券不在有效期内") } // 更新优惠券状态为已使用 return s.couponRepo.UseCoupon(userCouponID, orderID) } // ValidateCoupon 验证优惠券是否可用 func (s *CouponService) ValidateCoupon(userID, userCouponID uint, orderAmount float64) (*model.UserCoupon, float64, error) { // 获取用户优惠券 userCoupon, err := s.couponRepo.GetUserCouponByID(userCouponID) if err != nil { return nil, 0, errors.New("优惠券不存在") } // 检查是否属于该用户 if userCoupon.UserID != userID { return nil, 0, errors.New("无权使用该优惠券") } // 检查是否已使用 if userCoupon.Status != 0 { return nil, 0, errors.New("优惠券已使用或已过期") } // 检查优惠券是否在有效期内 now := time.Now() if now.Before(userCoupon.Coupon.StartTime) || now.After(userCoupon.Coupon.EndTime) { return nil, 0, errors.New("优惠券不在有效期内") } // 检查最低消费金额 minAmount := float64(userCoupon.Coupon.MinAmount) / 100 // 分转元 if orderAmount < minAmount { return nil, 0, errors.New(fmt.Sprintf("订单金额不满足优惠券使用条件,最低需要%.2f元", minAmount)) } // 计算优惠金额 var discountAmount float64 switch userCoupon.Coupon.Type { case 1: // 满减券 discountAmount = float64(userCoupon.Coupon.Value) / 100 // 分转元 case 2: // 折扣券 discountRate := float64(userCoupon.Coupon.Value) / 100 // 85 -> 0.85 discountAmount = orderAmount * (1 - discountRate) case 3: // 免邮券 discountAmount = 0 // 免邮券的优惠金额在运费中体现 default: return nil, 0, errors.New("不支持的优惠券类型") } // 确保优惠金额不超过订单金额 if discountAmount > orderAmount { discountAmount = orderAmount } return userCoupon, discountAmount, nil } // GetAvailableCouponsForOrder 获取订单可用的优惠券 func (s *CouponService) GetAvailableCouponsForOrder(userID uint, orderAmount float64) ([]model.UserCoupon, error) { // 获取用户未使用的优惠券 userCoupons, err := s.couponRepo.GetUserCoupons(userID, 1) // 1表示未使用(API状态值) if err != nil { return nil, err } var availableCoupons []model.UserCoupon now := time.Now() for _, userCoupon := range userCoupons { // 严格检查优惠券状态:必须是未使用状态(0)且没有关联订单 if userCoupon.Status != 0 || userCoupon.OrderID != nil { continue } // 检查是否在有效期内 if now.Before(userCoupon.Coupon.StartTime) || now.After(userCoupon.Coupon.EndTime) { continue } // 检查优惠券模板是否可用 if userCoupon.Coupon.Status != 1 { continue } // 检查最低消费金额 minAmount := float64(userCoupon.Coupon.MinAmount) / 100 // 分转元 if orderAmount >= minAmount { availableCoupons = append(availableCoupons, userCoupon) } } return availableCoupons, nil } // ==================== 管理端方法 ==================== // GetCouponListForAdmin 获取优惠券列表(管理端) func (s *CouponService) GetCouponListForAdmin(page, pageSize int, status, couponType, keyword string) ([]model.Coupon, int64, error) { return s.couponRepo.GetCouponListForAdmin(page, pageSize, status, couponType, keyword) } // GetCouponDetailForAdmin 获取优惠券详情(管理端) func (s *CouponService) GetCouponDetailForAdmin(couponID uint) (*model.Coupon, error) { coupon, err := s.couponRepo.GetByID(couponID) if err != nil { return nil, errors.New("优惠券不存在") } // 获取优惠券的使用统计(注意:领取数和使用数不在Coupon表中,而是在UserCoupon表中统计) // 这里仅作为查询,不修改coupon对象 _, _, _ = s.couponRepo.GetCouponUsageStats(couponID) return coupon, nil } // CreateCoupon 创建优惠券 func (s *CouponService) CreateCoupon(req interface{}, adminID uint) (*model.Coupon, error) { // 类型断言 type CreateCouponRequest struct { Name string Type int Value int64 MinAmount int64 Description string StartTime time.Time EndTime time.Time TotalCount int Status int } reqData, ok := req.(*CreateCouponRequest) if !ok { return nil, errors.New("无效的请求参数") } // 验证时间 if reqData.EndTime.Before(reqData.StartTime) { return nil, errors.New("结束时间不能早于开始时间") } // 验证优惠券类型和值 switch reqData.Type { case 1: // 满减券,value是金额(分) if reqData.Value <= 0 { return nil, errors.New("满减券优惠金额必须大于0") } case 2: // 折扣券,value是折扣(85表示8.5折) if reqData.Value <= 0 || reqData.Value > 100 { return nil, errors.New("折扣券折扣必须在0-100之间") } case 3: // 免邮券 // 免邮券不需要验证value default: return nil, errors.New("不支持的优惠券类型") } coupon := &model.Coupon{ Name: reqData.Name, Type: uint8(reqData.Type), Value: uint(reqData.Value), MinAmount: uint(reqData.MinAmount), Description: reqData.Description, StartTime: reqData.StartTime, EndTime: reqData.EndTime, TotalCount: uint(reqData.TotalCount), UsedCount: 0, Status: uint8(reqData.Status), CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := s.couponRepo.Create(coupon) if err != nil { return nil, fmt.Errorf("创建优惠券失败: %v", err) } return coupon, nil } // UpdateCoupon 更新优惠券 func (s *CouponService) UpdateCoupon(couponID uint, req interface{}) error { // 检查优惠券是否存在 _, err := s.couponRepo.GetByID(couponID) if err != nil { return errors.New("优惠券不存在") } // 类型断言 type UpdateCouponRequest struct { Name string Type int Value int64 MinAmount int64 Description string StartTime time.Time EndTime time.Time TotalCount int Status int } reqData, ok := req.(*UpdateCouponRequest) if !ok { return errors.New("无效的请求参数") } // 验证时间 if !reqData.EndTime.IsZero() && !reqData.StartTime.IsZero() { if reqData.EndTime.Before(reqData.StartTime) { return errors.New("结束时间不能早于开始时间") } } updates := make(map[string]interface{}) if reqData.Name != "" { updates["name"] = reqData.Name } if reqData.Type > 0 { updates["type"] = reqData.Type } if reqData.Value > 0 { updates["value"] = reqData.Value } if reqData.MinAmount >= 0 { updates["min_amount"] = reqData.MinAmount } if reqData.Description != "" { updates["description"] = reqData.Description } if !reqData.StartTime.IsZero() { updates["start_time"] = reqData.StartTime } if !reqData.EndTime.IsZero() { updates["end_time"] = reqData.EndTime } if reqData.TotalCount >= 0 { updates["total_count"] = reqData.TotalCount } if reqData.Status >= 0 { updates["status"] = reqData.Status } updates["updated_at"] = time.Now() return s.couponRepo.Update(couponID, updates) } // DeleteCoupon 删除优惠券 func (s *CouponService) DeleteCoupon(couponID uint) error { // 检查是否有用户已领取 hasUsers, err := s.couponRepo.CheckCouponHasUsers(couponID) if err != nil { return err } if hasUsers { return errors.New("该优惠券已被用户领取,无法删除") } return s.couponRepo.Delete(couponID) } // UpdateCouponStatus 更新优惠券状态 func (s *CouponService) UpdateCouponStatus(couponID uint, status int) error { _, err := s.couponRepo.GetByID(couponID) if err != nil { return errors.New("优惠券不存在") } updates := map[string]interface{}{ "status": status, "updated_at": time.Now(), } return s.couponRepo.Update(couponID, updates) } // BatchDeleteCoupons 批量删除优惠券 func (s *CouponService) BatchDeleteCoupons(couponIDs []uint) error { // 检查每个优惠券是否可以删除 for _, id := range couponIDs { hasUsers, err := s.couponRepo.CheckCouponHasUsers(id) if err != nil { return err } if hasUsers { return fmt.Errorf("优惠券ID %d 已被用户领取,无法删除", id) } } return s.couponRepo.BatchDelete(couponIDs) } // GetCouponStatistics 获取优惠券统计 func (s *CouponService) GetCouponStatistics(startTime, endTime time.Time) (map[string]interface{}, error) { ctx := context.Background() // 获取总优惠券数 totalCoupons, err := s.couponRepo.CountTotalCoupons(ctx) if err != nil { return nil, err } // 获取启用的优惠券数 activeCoupons, err := s.couponRepo.CountActiveCoupons(ctx) if err != nil { return nil, err } // 获取总领取数 totalReceived, err := s.couponRepo.CountTotalReceived(ctx, startTime, endTime) if err != nil { return nil, err } // 获取总使用数 totalUsed, err := s.couponRepo.CountTotalUsed(ctx, startTime, endTime) if err != nil { return nil, err } // 获取各类型优惠券统计 typeStats, err := s.couponRepo.GetCouponTypeStats(ctx) if err != nil { return nil, err } // 获取热门优惠券 topCoupons, err := s.couponRepo.GetTopCoupons(ctx, 10) if err != nil { return nil, err } // 计算使用率 useRate := 0.0 if totalReceived > 0 { useRate = float64(totalUsed) / float64(totalReceived) * 100 } stats := map[string]interface{}{ "total_coupons": totalCoupons, "active_coupons": activeCoupons, "total_received": totalReceived, "total_used": totalUsed, "use_rate": fmt.Sprintf("%.2f%%", useRate), "type_stats": typeStats, "top_coupons": topCoupons, } return stats, nil } // GetUserCouponListForAdmin 获取用户优惠券列表(管理端) func (s *CouponService) GetUserCouponListForAdmin(userID uint, page, pageSize int) ([]model.UserCoupon, int64, error) { return s.couponRepo.GetUserCouponListForAdmin(userID, page, pageSize) } // DistributeCoupon 发放优惠券 func (s *CouponService) DistributeCoupon(couponID uint, userIDs []uint, distributeAll bool, quantity int, adminID uint) (map[string]interface{}, error) { ctx := context.Background() // 验证优惠券是否存在 coupon, err := s.couponRepo.GetByID(couponID) if err != nil { return nil, errors.New("优惠券不存在") } // 检查优惠券状态 if coupon.Status != 1 { return nil, errors.New("优惠券已禁用,无法发放") } // 如果是全员发放,获取所有用户ID var targetUserIDs []uint distributeType := "single" if distributeAll { distributeType = "all" // TODO: 从用户表获取所有用户ID // targetUserIDs, err = s.userRepo.GetAllUserIDs() // 暂时返回错误,需要注入 userRepo return nil, errors.New("全员发放功能暂未实现") } else { targetUserIDs = userIDs if len(targetUserIDs) > 1 { distributeType = "batch" } } // 记录发放结果 successCount := 0 failCount := 0 // 给每个用户发放优惠券 for _, userID := range targetUserIDs { for i := 0; i < quantity; i++ { userCoupon := &model.UserCoupon{ UserID: userID, CouponID: couponID, Status: 0, // 未使用 CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := s.couponRepo.CreateUserCoupon(userCoupon) if err != nil { failCount++ } else { successCount++ } } } // TODO: 记录发放历史到数据库 // 需要创建 coupon_distribute_history 表 _ = ctx _ = adminID result := map[string]interface{}{ "total_count": len(targetUserIDs) * quantity, "success_count": successCount, "fail_count": failCount, "distribute_type": distributeType, } return result, nil } // GetDistributeHistory 获取发放历史 func (s *CouponService) GetDistributeHistory(page, pageSize int) ([]map[string]interface{}, int64, error) { return s.couponRepo.GetDistributeHistory(page, pageSize) }