256 lines
6.9 KiB
Go
256 lines
6.9 KiB
Go
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
|
|
}
|