实现已发放优惠券的管理接口

This commit is contained in:
2026-04-29 16:59:14 +08:00
parent ccb8db555e
commit a4d9c28702
7 changed files with 622 additions and 50 deletions

255
web/services/coupon_user.go Normal file
View File

@@ -0,0 +1,255 @@
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
}