package services import ( "errors" "platform/pkg/u" "platform/web/core" m "platform/web/models" q "platform/web/queries" "time" "gorm.io/gen/field" "gorm.io/gorm" ) var CouponUser = &couponUserService{} type couponUserService struct{} func (s *couponUserService) Create(data CreateCouponUserData) error { now := time.Now() status := u.Else(data.Status, m.CouponUserStatusUnused) if err := validateCouponUserStatus(status); err != nil { return err } return q.Q.Transaction(func(tx *q.Query) error { coupon, err := tx.Coupon.Where(tx.Coupon.ID.Eq(data.CouponID)).Take() if errors.Is(err, gorm.ErrRecordNotFound) { return core.NewBizErr("优惠券不存在") } if err != nil { return core.NewServErr("获取优惠券数据失败", err) } if coupon.Status != m.CouponStatusEnabled { return core.NewBizErr("优惠券不可用") } if coupon.Count <= 0 { return core.NewBizErr("优惠券已发放完") } _, err = tx.User.Where(tx.User.ID.Eq(data.UserID)).Take() if errors.Is(err, gorm.ErrRecordNotFound) { return core.NewBizErr("用户不存在") } if err != nil { return core.NewServErr("获取用户数据失败", err) } expireAt := data.ExpireAt if expireAt == nil { expireAt = couponUserExpireAt(coupon, now) } usedAt := data.UsedAt if status == m.CouponUserStatusUsed && usedAt == nil { usedAt = &now } if status == m.CouponUserStatusUnused { usedAt = nil } err = tx.CouponUser.Create(&m.CouponUser{ UserID: data.UserID, CouponID: data.CouponID, Status: status, ExpireAt: expireAt, UsedAt: usedAt, }) if err != nil { return core.NewServErr("发放优惠券失败", err) } return adjustCouponCount(tx, coupon.ID, -1) }) } type CreateCouponUserData struct { CouponID int32 `json:"coupon_id" validate:"required"` UserID int32 `json:"user_id" validate:"required"` Status *m.CouponUserStatus `json:"status"` ExpireAt *time.Time `json:"expire_at"` UsedAt *time.Time `json:"used_at"` } func (s *couponUserService) Update(data UpdateCouponUserData) error { return q.Q.Transaction(func(tx *q.Query) error { current, err := tx.CouponUser.Where(tx.CouponUser.ID.Eq(data.ID)).Take() if errors.Is(err, gorm.ErrRecordNotFound) { return core.NewBizErr("已发放优惠券不存在") } if err != nil { return core.NewServErr("获取已发放优惠券失败", err) } do := make([]field.AssignExpr, 0) if data.ExpireAtClear != nil && *data.ExpireAtClear { do = append(do, tx.CouponUser.ExpireAt.Null()) } else if data.ExpireAt != nil { do = append(do, tx.CouponUser.ExpireAt.Value(*data.ExpireAt)) } if data.UsedAtClear != nil && *data.UsedAtClear { do = append(do, tx.CouponUser.UsedAt.Null()) } else if data.UsedAt != nil { do = append(do, tx.CouponUser.UsedAt.Value(*data.UsedAt)) } if data.Status != nil { if err := validateCouponUserStatus(*data.Status); err != nil { return err } if current.Status != *data.Status { if current.Status == m.CouponUserStatusUsed { return core.NewBizErr("已使用的优惠券不能修改状态") } if current.Status == m.CouponUserStatusDisabled && *data.Status == m.CouponUserStatusUsed { return core.NewBizErr("已禁用的优惠券不能标记为已使用") } switch *data.Status { case m.CouponUserStatusUnused: if current.Status == m.CouponUserStatusDisabled { if err := adjustCouponCount(tx, current.CouponID, -1); err != nil { return err } } if data.UsedAt == nil && (data.UsedAtClear == nil || !*data.UsedAtClear) { do = append(do, tx.CouponUser.UsedAt.Null()) } case m.CouponUserStatusUsed: if data.UsedAt == nil && (data.UsedAtClear == nil || !*data.UsedAtClear) { do = append(do, tx.CouponUser.UsedAt.Value(time.Now())) } case m.CouponUserStatusDisabled: if current.Status == m.CouponUserStatusUnused { if err := adjustCouponCount(tx, current.CouponID, 1); err != nil { return err } } } do = append(do, tx.CouponUser.Status.Value(int(*data.Status))) } } if len(do) == 0 { return nil } result, err := tx.CouponUser. Where( tx.CouponUser.ID.Eq(data.ID), tx.CouponUser.Status.Eq(int(current.Status)), ). UpdateSimple(do...) if err != nil { return core.NewServErr("更新已发放优惠券失败", err) } if result.RowsAffected == 0 { return core.NewBizErr("已发放优惠券状态已变化,请重试") } return nil }) } type UpdateCouponUserData struct { ID int32 `json:"id" validate:"required"` Status *m.CouponUserStatus `json:"status"` ExpireAt *time.Time `json:"expire_at"` ExpireAtClear *bool `json:"expire_at_clear"` UsedAt *time.Time `json:"used_at"` UsedAtClear *bool `json:"used_at_clear"` } func (s *couponUserService) Delete(id int32) error { status := m.CouponUserStatusDisabled return s.Update(UpdateCouponUserData{ ID: id, Status: &status, }) } func (s *couponUserService) DeleteOfUser(id int32, userID int32) error { assigned, err := q.CouponUser.Where( q.CouponUser.ID.Eq(id), q.CouponUser.UserID.Eq(userID), ).Take() if errors.Is(err, gorm.ErrRecordNotFound) { return core.NewBizErr("已发放优惠券不存在") } if err != nil { return core.NewServErr("获取已发放优惠券失败", err) } if assigned.Status != m.CouponUserStatusUnused { return core.NewBizErr("只能撤销未使用的优惠券") } return s.Delete(id) } func couponUserExpireAt(coupon *m.Coupon, now time.Time) *time.Time { if coupon == nil { return nil } switch coupon.ExpireType { case m.CouponExpireTypeFixed: return coupon.ExpireAt case m.CouponExpireTypeRelative: if coupon.ExpireIn == nil { return nil } expireAt := now.Add(time.Duration(*coupon.ExpireIn) * 24 * time.Hour) return &expireAt default: return nil } } func validateCouponUserStatus(status m.CouponUserStatus) error { switch status { case m.CouponUserStatusUnused, m.CouponUserStatusUsed, m.CouponUserStatusDisabled: return nil default: return core.NewBizErr("优惠券发放状态无效") } } func adjustCouponCount(tx *q.Query, couponID int32, delta int32) error { coupon, err := tx.Coupon.Where(tx.Coupon.ID.Eq(couponID)).Take() if errors.Is(err, gorm.ErrRecordNotFound) { return core.NewBizErr("优惠券不存在") } if err != nil { return core.NewServErr("获取优惠券数据失败", err) } next := coupon.Count + delta if next < 0 { return core.NewBizErr("优惠券已发放完") } result, err := tx.Coupon. Where(tx.Coupon.ID.Eq(couponID), tx.Coupon.Count_.Eq(coupon.Count)). UpdateSimple(tx.Coupon.Count_.Value(next)) if err != nil { return core.NewServErr("更新优惠券数量失败", err) } if result.RowsAffected == 0 { return core.NewBizErr("优惠券库存已变化,请重试") } return nil }