Files
2025-11-28 15:18:10 +08:00

563 lines
15 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}