实现已发放优惠券的管理接口
This commit is contained in:
255
web/services/coupon_user.go
Normal file
255
web/services/coupon_user.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user