实现已发放优惠券的管理接口
This commit is contained in:
@@ -127,7 +127,8 @@ insert into permission (name, description, sort) values
|
|||||||
('trade', '交易', 12),
|
('trade', '交易', 12),
|
||||||
('bill', '账单', 13),
|
('bill', '账单', 13),
|
||||||
('balance_activity', '余额变动', 14),
|
('balance_activity', '余额变动', 14),
|
||||||
('proxy', '代理', 15);
|
('proxy', '代理', 15),
|
||||||
|
('coupon_user', '已发放优惠券', 16);
|
||||||
|
|
||||||
-- --------------------------
|
-- --------------------------
|
||||||
-- level 2
|
-- level 2
|
||||||
@@ -209,6 +210,11 @@ insert into permission (parent_id, name, description, sort) values
|
|||||||
insert into permission (parent_id, name, description, sort) values
|
insert into permission (parent_id, name, description, sort) values
|
||||||
((select id from permission where name = 'balance_activity' and deleted_at is null), 'balance_activity:read', '读取余额变动列表', 1);
|
((select id from permission where name = 'balance_activity' and deleted_at is null), 'balance_activity:read', '读取余额变动列表', 1);
|
||||||
|
|
||||||
|
-- coupon_user 子权限
|
||||||
|
insert into permission (parent_id, name, description, sort) values
|
||||||
|
((select id from permission where name = 'coupon_user' and deleted_at is null), 'coupon_user:read', '读取已发放优惠券列表', 1),
|
||||||
|
((select id from permission where name = 'coupon_user' and deleted_at is null), 'coupon_user:write', '编辑已发放优惠券', 2);
|
||||||
|
|
||||||
-- --------------------------
|
-- --------------------------
|
||||||
-- level 3
|
-- level 3
|
||||||
-- --------------------------
|
-- --------------------------
|
||||||
@@ -263,6 +269,10 @@ insert into permission (parent_id, name, description, sort) values
|
|||||||
insert into permission (parent_id, name, description, sort) values
|
insert into permission (parent_id, name, description, sort) values
|
||||||
((select id from permission where name = 'coupon:write' and deleted_at is null), 'coupon:write:assign', '发放优惠券', 1);
|
((select id from permission where name = 'coupon:write' and deleted_at is null), 'coupon:write:assign', '发放优惠券', 1);
|
||||||
|
|
||||||
|
-- coupon_user:read 子权限
|
||||||
|
insert into permission (parent_id, name, description, sort) values
|
||||||
|
((select id from permission where name = 'coupon_user:read' and deleted_at is null), 'coupon_user:read:of_user', '读取指定用户的已发放优惠券列表', 1);
|
||||||
|
|
||||||
-- --------------------------
|
-- --------------------------
|
||||||
-- level 4
|
-- level 4
|
||||||
-- --------------------------
|
-- --------------------------
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ const (
|
|||||||
ScopeCouponWrite = string("coupon:write") // 写入优惠券
|
ScopeCouponWrite = string("coupon:write") // 写入优惠券
|
||||||
ScopeCouponWriteAssign = string("coupon:write:assign") // 发放优惠券
|
ScopeCouponWriteAssign = string("coupon:write:assign") // 发放优惠券
|
||||||
|
|
||||||
|
ScopeCouponUser = string("coupon_user") // 用户优惠券
|
||||||
|
ScopeCouponUserRead = string("coupon_user:read") // 读取用户优惠券列表
|
||||||
|
ScopeCouponUserReadOfUser = string("coupon_user:read:of_user") // 读取指定用户的用户优惠券列表
|
||||||
|
ScopeCouponUserWrite = string("coupon_user:write") // 写入用户优惠券
|
||||||
|
|
||||||
ScopeBatch = string("batch") // 批次
|
ScopeBatch = string("batch") // 批次
|
||||||
ScopeBatchRead = string("batch:read") // 读取批次列表
|
ScopeBatchRead = string("batch:read") // 读取批次列表
|
||||||
ScopeBatchReadOfUser = string("batch:read:of_user") // 读取指定用户的批次列表
|
ScopeBatchReadOfUser = string("batch:read:of_user") // 读取指定用户的批次列表
|
||||||
|
|||||||
330
web/handlers/coupon_user.go
Normal file
330
web/handlers/coupon_user.go
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"platform/pkg/u"
|
||||||
|
"platform/web/auth"
|
||||||
|
"platform/web/core"
|
||||||
|
g "platform/web/globals"
|
||||||
|
m "platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
|
s "platform/web/services"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageCouponUser 分页查询当前用户已发放优惠券
|
||||||
|
func PageCouponUser(c *fiber.Ctx) error {
|
||||||
|
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req PageCouponUserReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conds := couponUserPageConditions(req.CouponUserPageFilter)
|
||||||
|
conds = append(conds, q.CouponUser.UserID.Eq(authCtx.User.ID))
|
||||||
|
|
||||||
|
list, total, err := q.CouponUser.
|
||||||
|
Joins(q.CouponUser.Coupon).
|
||||||
|
Select(couponUserSelect(false)...).
|
||||||
|
Where(conds...).
|
||||||
|
Order(q.CouponUser.CreatedAt.Desc()).
|
||||||
|
FindByPage(req.GetOffset(), req.GetLimit())
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("获取数据失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(core.PageResp{
|
||||||
|
List: list,
|
||||||
|
Total: int(total),
|
||||||
|
Page: req.GetPage(),
|
||||||
|
Size: req.GetSize(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageCouponUserReq struct {
|
||||||
|
core.PageReq
|
||||||
|
CouponUserPageFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCouponUser 获取当前用户已发放优惠券详情
|
||||||
|
func GetCouponUser(c *fiber.Ctx) error {
|
||||||
|
authCtx, err := auth.GetAuthCtx(c).PermitUser()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req core.IdReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := q.CouponUser.
|
||||||
|
Joins(q.CouponUser.Coupon).
|
||||||
|
Select(couponUserSelect(false)...).
|
||||||
|
Where(
|
||||||
|
q.CouponUser.ID.Eq(req.Id),
|
||||||
|
q.CouponUser.UserID.Eq(authCtx.User.ID),
|
||||||
|
).
|
||||||
|
Take()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return core.NewBizErr("已发放优惠券不存在")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("获取数据失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageCouponUserByAdmin 分页查询全部已发放优惠券
|
||||||
|
func PageCouponUserByAdmin(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeCouponUserRead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req PageCouponUserByAdminReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conds := couponUserPageConditions(req.CouponUserPageFilter)
|
||||||
|
if req.UserID != nil {
|
||||||
|
conds = append(conds, q.CouponUser.UserID.Eq(*req.UserID))
|
||||||
|
}
|
||||||
|
if req.UserPhone != nil {
|
||||||
|
conds = append(conds, q.User.As("User").Phone.Eq(*req.UserPhone))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, total, err := q.CouponUser.
|
||||||
|
Joins(q.CouponUser.Coupon, q.CouponUser.User).
|
||||||
|
Select(couponUserSelect(true)...).
|
||||||
|
Where(conds...).
|
||||||
|
Order(q.CouponUser.CreatedAt.Desc()).
|
||||||
|
FindByPage(req.GetOffset(), req.GetLimit())
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("获取数据失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(core.PageResp{
|
||||||
|
List: list,
|
||||||
|
Total: int(total),
|
||||||
|
Page: req.GetPage(),
|
||||||
|
Size: req.GetSize(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageCouponUserByAdminReq struct {
|
||||||
|
core.PageReq
|
||||||
|
CouponUserPageFilter
|
||||||
|
UserID *int32 `json:"user_id,omitempty"`
|
||||||
|
UserPhone *string `json:"user_phone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageCouponUserOfUserByAdmin 分页查询指定用户已发放优惠券
|
||||||
|
func PageCouponUserOfUserByAdmin(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeCouponUserReadOfUser)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req PageCouponUserOfUserByAdminReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conds := couponUserPageConditions(req.CouponUserPageFilter)
|
||||||
|
conds = append(conds, q.CouponUser.UserID.Eq(req.UserID))
|
||||||
|
|
||||||
|
list, total, err := q.CouponUser.
|
||||||
|
Joins(q.CouponUser.Coupon, q.CouponUser.User).
|
||||||
|
Select(couponUserSelect(true)...).
|
||||||
|
Where(conds...).
|
||||||
|
Order(q.CouponUser.CreatedAt.Desc()).
|
||||||
|
FindByPage(req.GetOffset(), req.GetLimit())
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("获取数据失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(core.PageResp{
|
||||||
|
List: list,
|
||||||
|
Total: int(total),
|
||||||
|
Page: req.GetPage(),
|
||||||
|
Size: req.GetSize(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageCouponUserOfUserByAdminReq struct {
|
||||||
|
core.PageReq
|
||||||
|
CouponUserPageFilter
|
||||||
|
UserID int32 `json:"user_id" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCouponUserByAdmin 获取已发放优惠券详情
|
||||||
|
func GetCouponUserByAdmin(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeCouponUserRead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req core.IdReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := q.CouponUser.
|
||||||
|
Joins(q.CouponUser.Coupon, q.CouponUser.User).
|
||||||
|
Select(couponUserSelect(true)...).
|
||||||
|
Where(q.CouponUser.ID.Eq(req.Id)).
|
||||||
|
Take()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return core.NewBizErr("已发放优惠券不存在")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return core.NewBizErr("获取数据失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateCouponUserByAdmin(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeCouponUserWrite)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req s.CreateCouponUserData
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.CouponUser.Create(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateCouponUserByAdmin(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeCouponUserWrite)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req s.UpdateCouponUserData
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.CouponUser.Update(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteCouponUserByAdmin(c *fiber.Ctx) error {
|
||||||
|
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeCouponUserWrite)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var req core.IdReq
|
||||||
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.CouponUser.Delete(req.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CouponUserPageFilter struct {
|
||||||
|
CouponID *int32 `json:"coupon_id,omitempty"`
|
||||||
|
CouponName *string `json:"coupon_name,omitempty"`
|
||||||
|
Status *m.CouponUserStatus `json:"status,omitempty"`
|
||||||
|
Expired *bool `json:"expired,omitempty"`
|
||||||
|
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
|
||||||
|
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
|
||||||
|
ExpireAtStart *time.Time `json:"expire_at_start,omitempty"`
|
||||||
|
ExpireAtEnd *time.Time `json:"expire_at_end,omitempty"`
|
||||||
|
UsedAtStart *time.Time `json:"used_at_start,omitempty"`
|
||||||
|
UsedAtEnd *time.Time `json:"used_at_end,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func couponUserPageConditions(req CouponUserPageFilter) []gen.Condition {
|
||||||
|
conds := make([]gen.Condition, 0)
|
||||||
|
if req.CouponID != nil {
|
||||||
|
conds = append(conds, q.CouponUser.CouponID.Eq(*req.CouponID))
|
||||||
|
}
|
||||||
|
if req.CouponName != nil {
|
||||||
|
conds = append(conds, q.Coupon.As("Coupon").Name.Like("%"+*req.CouponName+"%"))
|
||||||
|
}
|
||||||
|
if req.Status != nil {
|
||||||
|
conds = append(conds, q.CouponUser.Status.Eq(int(*req.Status)))
|
||||||
|
}
|
||||||
|
if req.Expired != nil {
|
||||||
|
if *req.Expired {
|
||||||
|
conds = append(conds, q.CouponUser.ExpireAt.IsNotNull(), q.CouponUser.ExpireAt.Lte(time.Now()))
|
||||||
|
} else {
|
||||||
|
conds = append(conds, q.CouponUser.Where(q.CouponUser.ExpireAt.IsNull()).Or(q.CouponUser.ExpireAt.Gt(time.Now())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.CreatedAtStart != nil {
|
||||||
|
conds = append(conds, q.CouponUser.CreatedAt.Gte(u.DateHead(*req.CreatedAtStart)))
|
||||||
|
}
|
||||||
|
if req.CreatedAtEnd != nil {
|
||||||
|
conds = append(conds, q.CouponUser.CreatedAt.Lte(u.DateTail(*req.CreatedAtEnd)))
|
||||||
|
}
|
||||||
|
if req.ExpireAtStart != nil {
|
||||||
|
conds = append(conds, q.CouponUser.ExpireAt.Gte(u.DateHead(*req.ExpireAtStart)))
|
||||||
|
}
|
||||||
|
if req.ExpireAtEnd != nil {
|
||||||
|
conds = append(conds, q.CouponUser.ExpireAt.Lte(u.DateTail(*req.ExpireAtEnd)))
|
||||||
|
}
|
||||||
|
if req.UsedAtStart != nil {
|
||||||
|
conds = append(conds, q.CouponUser.UsedAt.Gte(u.DateHead(*req.UsedAtStart)))
|
||||||
|
}
|
||||||
|
if req.UsedAtEnd != nil {
|
||||||
|
conds = append(conds, q.CouponUser.UsedAt.Lte(u.DateTail(*req.UsedAtEnd)))
|
||||||
|
}
|
||||||
|
return conds
|
||||||
|
}
|
||||||
|
|
||||||
|
func couponUserSelect(includeUser bool) []field.Expr {
|
||||||
|
cols := []field.Expr{
|
||||||
|
q.CouponUser.ALL,
|
||||||
|
q.Coupon.As("Coupon").ID.As("Coupon__id"),
|
||||||
|
q.Coupon.As("Coupon").Name.As("Coupon__name"),
|
||||||
|
q.Coupon.As("Coupon").Amount.As("Coupon__amount"),
|
||||||
|
q.Coupon.As("Coupon").MinAmount.As("Coupon__min_amount"),
|
||||||
|
q.Coupon.As("Coupon").Count_.As("Coupon__count"),
|
||||||
|
q.Coupon.As("Coupon").Status.As("Coupon__status"),
|
||||||
|
q.Coupon.As("Coupon").ExpireType.As("Coupon__expire_type"),
|
||||||
|
q.Coupon.As("Coupon").ExpireAt.As("Coupon__expire_at"),
|
||||||
|
q.Coupon.As("Coupon").ExpireIn.As("Coupon__expire_in"),
|
||||||
|
q.Coupon.As("Coupon").CreatedAt.As("Coupon__created_at"),
|
||||||
|
q.Coupon.As("Coupon").UpdatedAt.As("Coupon__updated_at"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if includeUser {
|
||||||
|
cols = append(cols,
|
||||||
|
q.User.As("User").ID.As("User__id"),
|
||||||
|
q.User.As("User").Phone.As("User__phone"),
|
||||||
|
q.User.As("User").Name.As("User__name"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols
|
||||||
|
}
|
||||||
@@ -22,4 +22,5 @@ type CouponUserStatus int
|
|||||||
const (
|
const (
|
||||||
CouponUserStatusUnused CouponUserStatus = 0 // 未使用
|
CouponUserStatusUnused CouponUserStatus = 0 // 未使用
|
||||||
CouponUserStatusUsed CouponUserStatus = 1 // 已使用
|
CouponUserStatusUsed CouponUserStatus = 1 // 已使用
|
||||||
|
CouponUserStatusDisabled CouponUserStatus = 2 // 已禁用
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ func userRouter(api fiber.Router) {
|
|||||||
balance := api.Group("/balance")
|
balance := api.Group("/balance")
|
||||||
balance.Post("/page", handlers.PageBalanceActivity)
|
balance.Post("/page", handlers.PageBalanceActivity)
|
||||||
|
|
||||||
|
// 已发放优惠券
|
||||||
|
couponUser := api.Group("/coupon-user")
|
||||||
|
couponUser.Post("/page", handlers.PageCouponUser)
|
||||||
|
couponUser.Post("/get", handlers.GetCouponUser)
|
||||||
|
|
||||||
// 公告
|
// 公告
|
||||||
announcement := api.Group("/announcement")
|
announcement := api.Group("/announcement")
|
||||||
announcement.Post("/list", handlers.ListAnnouncements)
|
announcement.Post("/list", handlers.ListAnnouncements)
|
||||||
@@ -254,4 +259,13 @@ func adminRouter(api fiber.Router) {
|
|||||||
coupon.Post("/update", handlers.UpdateCoupon)
|
coupon.Post("/update", handlers.UpdateCoupon)
|
||||||
coupon.Post("/remove", handlers.DeleteCoupon)
|
coupon.Post("/remove", handlers.DeleteCoupon)
|
||||||
coupon.Post("/update/assign", handlers.AssignCoupon)
|
coupon.Post("/update/assign", handlers.AssignCoupon)
|
||||||
|
|
||||||
|
// coupon-user 已发放优惠券
|
||||||
|
var couponUser = api.Group("/coupon-user")
|
||||||
|
couponUser.Post("/page", handlers.PageCouponUserByAdmin)
|
||||||
|
couponUser.Post("/page/of-user", handlers.PageCouponUserOfUserByAdmin)
|
||||||
|
couponUser.Post("/get", handlers.GetCouponUserByAdmin)
|
||||||
|
couponUser.Post("/create", handlers.CreateCouponUserByAdmin)
|
||||||
|
couponUser.Post("/update", handlers.UpdateCouponUserByAdmin)
|
||||||
|
couponUser.Post("/remove", handlers.DeleteCouponUserByAdmin)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,54 +108,11 @@ func (s *couponService) Delete(id int32) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发放优惠券
|
|
||||||
func (s *couponService) Assign(couponID int32, userID int32) error {
|
func (s *couponService) Assign(couponID int32, userID int32) error {
|
||||||
// 获取优惠券信息
|
return CouponUser.Create(CreateCouponUserData{
|
||||||
coupon, err := q.Coupon.Where(q.Coupon.ID.Eq(couponID)).Take()
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return core.NewBizErr("优惠券不存在")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return core.NewBizErr("获取优惠券数据失败", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查优惠券是否可用
|
|
||||||
// 1. 状态必须为正常
|
|
||||||
if coupon.Status != m.CouponStatusEnabled {
|
|
||||||
return core.NewBizErr("优惠券不可用")
|
|
||||||
}
|
|
||||||
// 2. 数量必须大于0
|
|
||||||
if coupon.Count <= 0 {
|
|
||||||
return core.NewBizErr("优惠券已发放完")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发放优惠券
|
|
||||||
err = q.Q.Transaction(func(q *q.Query) error {
|
|
||||||
// 创建用户优惠券记录
|
|
||||||
err := q.CouponUser.Create(&m.CouponUser{
|
|
||||||
UserID: userID,
|
|
||||||
CouponID: couponID,
|
CouponID: couponID,
|
||||||
Status: m.CouponUserStatusUnused,
|
UserID: userID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扣减优惠券数量
|
|
||||||
_, err = q.Coupon.
|
|
||||||
Where(q.Coupon.ID.Eq(couponID), q.Coupon.Count_.Eq(coupon.Count)).
|
|
||||||
UpdateSimple(q.Coupon.Count_.Value(coupon.Count - 1))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return core.NewBizErr("发放优惠券失败", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserCoupon 获取用户的指定优惠券
|
// GetUserCoupon 获取用户的指定优惠券
|
||||||
@@ -165,7 +122,7 @@ func (s *couponService) GetUserCoupon(uid int32, cuid int32, amount decimal.Deci
|
|||||||
q.CouponUser.ID.Eq(cuid),
|
q.CouponUser.ID.Eq(cuid),
|
||||||
q.CouponUser.UserID.Eq(uid),
|
q.CouponUser.UserID.Eq(uid),
|
||||||
q.CouponUser.Status.Eq(int(m.CouponUserStatusUnused)),
|
q.CouponUser.Status.Eq(int(m.CouponUserStatusUnused)),
|
||||||
q.CouponUser.ExpireAt.Gt(time.Now()),
|
q.CouponUser.Where(q.CouponUser.ExpireAt.IsNull()).Or(q.CouponUser.ExpireAt.Gt(time.Now())),
|
||||||
).Take()
|
).Take()
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, core.NewBizErr("优惠券不存在或已失效")
|
return nil, core.NewBizErr("优惠券不存在或已失效")
|
||||||
|
|||||||
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