mh_goadmin_server/app/admin/models/marketing.go

579 lines
19 KiB
Go
Raw Normal View History

package models
import (
"errors"
"fmt"
utils "go-admin/app/admin/models/tools"
orm "go-admin/common/global"
"go-admin/logger"
"log"
"time"
)
const (
ErpCouponUnStarted = 1 // 未开始
ErpCouponSending = 2 // 发放中
ErpCouponInProgress = 3 // 进行中
ErpCouponFinished = 4 // 已结束
ErpCouponSendFailed = 5 // 发放失败
TaskInProgress = "in_progress" // 执行中
TaskCompleted = "completed" // 已完成
TaskFailed = "failed" // 发送失败
)
// CouponIssuanceTask 优惠券发放任务表
type CouponIssuanceTask struct {
Model
ErpCouponId uint32 `json:"erp_coupon_id"` // 优惠券id
ErpCouponName string `json:"erp_coupon_name"` // 优惠券名称
Status string `json:"status"` // 任务状态in_progress(执行中、completed已完成、failed发送失败
LastUserID uint32 `json:"last_user_id"` // 上次处理的用户ID
ErrorMessage string `json:"error_message" gorm:"type:text"` // 错误信息,若任务失败时记录
}
// ErpCoupon 营销管理-优惠券
type ErpCoupon struct {
Model
Name string `json:"name" gorm:"index"` // 优惠券名称
Remark string `json:"remark"` // 名称备注
Describe string `json:"describe" gorm:"type:text"` // 优惠券简介
Rule string `json:"rule" gorm:"type:text"` // 优惠券使用规则
CategoryNumber string `json:"category_number"` // 可以使用该优惠券的商品分类,如果为空则表示没限制
ActiveDate uint32 `json:"active_date"` // 有效期(天)
Amount uint32 `json:"amount"` // 金额(元)
UserType uint32 `json:"user_type"` // 领取人限制1-所有人 2-未付费用户 3-已付费用户 4-尊享会员
Limit uint32 `json:"limit"` // 优惠券叠加限制 0-不限制1-仅限原价购买时使用
State uint32 `json:"state" gorm:"index"` // 当前状态 1-未开始2-发放中3-进行中4-已结束5-发放失败
SendCount uint32 `json:"send_count"` // 已发放
UsedCount uint32 `json:"used_count"` // 已使用
TotalPayAmount float64 `json:"total_pay_amount"` // 支付金额(元)
PerCustomerAmount float64 `json:"per_customer_amount"` // 客单价(元)
SmsContent string `json:"sms_content" gorm:"type:text"` // 短信提示内容
}
// ErpMarketingCouponListReq 优惠券列表入参
type ErpMarketingCouponListReq struct {
Name string `json:"name" gorm:"index"` // 优惠券名称
State uint32 `json:"state" gorm:"index"` // 当前状态 1-未开始2-发放中3-进行中4-已结束
CreatedTimeStart string `json:"created_time_start"` // 创建开始时间
CreatedTimeEnd string `json:"created_time_end"` // 创建结束时间
PageIndex int `json:"pageIndex"` // 页码
PageSize int `json:"pageSize"` // 页面条数
IsExport uint32 `json:"is_export"` // 1-导出
}
// ErpMarketingCouponListResp 优惠券列表出参
type ErpMarketingCouponListResp struct {
List []ErpCoupon `json:"list"`
Total int `json:"total"` // 总条数
PageIndex int `json:"pageIndex"` // 页码
PageSize int `json:"pageSize"` // 页面条数
ExportUrl string `json:"export_url"` // 导出excel路径
}
// ErpMarketingCouponCreateReq 新增优惠券入参
type ErpMarketingCouponCreateReq struct {
Name string `json:"name" validate:"required"` // 优惠券名称
Remark string `json:"remark"` // 名称备注
Describe string `json:"describe" gorm:"type:text"` // 优惠券简介
Rule string `json:"rule" gorm:"type:text"` // 优惠券使用规则
CategoryNumber string `json:"category_number" validate:"required"` // 可以使用该优惠券的商品分类编号,如果为空则表示没限制
ActiveDate uint32 `json:"active_date" validate:"required"` // 有效期(天)
Amount uint32 `json:"amount" validate:"required"` // 金额(元)
UserType uint32 `json:"user_type" validate:"required"` // 领取人限制1-所有人 2-未付费用户 3-已付费用户 4-尊享会员
Limit uint32 `json:"limit"` // 优惠券叠加限制 0-不限制1-仅限原价购买时使用
}
// ErpMarketingCouponEditReq 编辑优惠券入参
type ErpMarketingCouponEditReq struct {
ErpCouponId uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id
Name string `json:"name" validate:"required"` // 优惠券名称
Remark string `json:"remark"` // 名称备注
Describe string `json:"describe" gorm:"type:text"` // 优惠券简介
Rule string `json:"rule" gorm:"type:text"` // 优惠券使用规则
CategoryNumber string `json:"category_number" validate:"required"` // 可以使用该优惠券的商品分类,如果为空则表示没限制
ActiveDate uint32 `json:"active_date" validate:"required"` // 有效期(天)
Amount uint32 `json:"amount" validate:"required"` // 金额(元)
UserType uint32 `json:"user_type" validate:"required"` // 领取人限制1-所有人 2-未付费用户 3-已付费用户 4-尊享会员
Limit uint32 `json:"limit"` // 优惠券叠加限制 0-不限制1-仅限原价购买时使用
}
// ErpMarketingCouponDeleteReq 删除优惠券入参
type ErpMarketingCouponDeleteReq struct {
ErpCouponId uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id
}
// ErpMarketingCouponStartReq 启动优惠券发放
type ErpMarketingCouponStartReq struct {
ErpCouponId []uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id
Remark string `json:"remark" validate:"required"` // 活动名称备注
SmsContent string `json:"sms_content" validate:"required"` // 短信提示内容
}
// ErpMarketingCouponDataReq 优惠券数据入参
type ErpMarketingCouponDataReq struct {
ErpCouponId uint32 `json:"erp_coupon_id" validate:"required"` // 优惠券id
}
// ErpMarketingCouponDataResp 优惠券数据出参
type ErpMarketingCouponDataResp struct {
}
// List 查询优惠券列表
func (m *ErpMarketingCouponListReq) List() (*ErpMarketingCouponListResp, error) {
resp := &ErpMarketingCouponListResp{
PageIndex: m.PageIndex,
PageSize: m.PageSize,
}
page := m.PageIndex - 1
if page < 0 {
page = 0
}
if m.PageSize == 0 {
m.PageSize = 10
}
qs := orm.Eloquent.Table("erp_coupon")
if m.Name != "" {
qs = qs.Where("name = ?", m.Name)
}
if m.State != 0 {
qs = qs.Where("state = ?", m.State)
}
if m.CreatedTimeStart != "" {
parse, err := time.Parse(QueryTimeFormat, m.CreatedTimeStart)
if err != nil {
logger.Errorf("erpPurchaseOrderList err:", err)
return nil, err
}
qs = qs.Where("created_at > ?", parse)
}
if m.CreatedTimeEnd != "" {
parse, err := time.Parse(QueryTimeFormat, m.CreatedTimeEnd)
if err != nil {
logger.Errorf("erpPurchaseOrderList err:", err)
return nil, err
}
qs = qs.Where("created_at < ?", parse)
}
var count int64
err := qs.Count(&count).Error
if err != nil {
logger.Error("count err:", logger.Field("err", err))
return resp, err
}
resp.Total = int(count)
var couponList []ErpCoupon
err = qs.Order("id DESC").Offset(page * m.PageSize).Limit(m.PageSize).Find(&couponList).Error
if err != nil && err != RecordNotFound {
logger.Error("erp_coupon list err:", logger.Field("err", err))
return resp, err
}
resp.List = couponList
return resp, nil
}
// CreateErpMarketingCoupon 新增优惠券
func CreateErpMarketingCoupon(req *ErpMarketingCouponCreateReq) error {
erpCoupon := &ErpCoupon{
Name: req.Name,
Remark: req.Remark,
Describe: req.Describe,
Rule: req.Rule,
CategoryNumber: req.CategoryNumber,
ActiveDate: req.ActiveDate,
Amount: req.Amount,
UserType: req.UserType,
Limit: req.Limit,
State: ErpCouponUnStarted,
}
err := orm.Eloquent.Create(erpCoupon).Error
if err != nil {
logger.Error("create purchase order err:", logger.Field("err", err))
return err
}
coupon := &Coupon{
Name: req.Name,
Describe: req.Describe,
Rule: req.Rule,
CouponType: "deduction",
Value: req.Amount,
CategoryNumber: req.CategoryNumber,
ErpCouponId: erpCoupon.ID,
}
err = orm.Eloquent.Create(coupon).Error
if err != nil {
logger.Error("create coupon order err:", logger.Field("err", err))
return err
}
return nil
}
// EditErpMarketingCoupon 编辑优惠券
func EditErpMarketingCoupon(req *ErpMarketingCouponEditReq) error {
// 查询订单信息
var erpCoupon ErpCoupon
err := orm.Eloquent.Table("erp_coupon").Where("id=?", req.ErpCouponId).Find(&erpCoupon).Error
if err != nil {
logger.Error("query erp_coupon err:", logger.Field("err", err))
return err
}
if erpCoupon.ID == 0 {
logger.Error("delete err, erpCoupon ID is:", logger.Field("erpCoupon.ID", req.ErpCouponId))
return errors.New(fmt.Sprintf("编辑失败:未查询到优惠券id[%d]", req.ErpCouponId))
}
// 1-更新优惠券信息
erpCoupon.Name = req.Name
erpCoupon.Remark = req.Remark
erpCoupon.Rule = req.Rule
erpCoupon.Describe = req.Describe
erpCoupon.CategoryNumber = req.CategoryNumber
erpCoupon.ActiveDate = req.ActiveDate
erpCoupon.Amount = req.Amount
erpCoupon.UserType = req.UserType
erpCoupon.Limit = req.Limit
begin := orm.Eloquent.Begin()
err = begin.Model(&ErpCoupon{}).Where("id = ?", req.ErpCouponId).
Omit("created_at").Save(erpCoupon).Error
if err != nil {
begin.Rollback()
logger.Error("update erp_coupon err:", logger.Field("err", err))
return err
}
// 查询订单信息
var coupon Coupon
err = orm.Eloquent.Table("coupon").Where("erp_coupon_id=?", req.ErpCouponId).Find(&coupon).Error
if err != nil {
begin.Rollback()
logger.Error("query coupon err:", logger.Field("err", err))
return err
}
coupon.Name = req.Name
coupon.Describe = req.Describe
coupon.Rule = req.Rule
coupon.Value = req.Amount
err = begin.Model(&Coupon{}).Where("erp_coupon_id = ?", req.ErpCouponId).
Omit("created_at").Save(coupon).Error
if err != nil {
begin.Rollback()
logger.Error("update coupon err:", logger.Field("err", err))
return err
}
err = begin.Commit().Error
if err != nil {
begin.Rollback()
logger.Error("commit update erp_coupon err:", logger.Field("err", err))
return err
}
return nil
}
// DeleteErpMarketingCoupon 删除优惠券
func DeleteErpMarketingCoupon(req *ErpMarketingCouponDeleteReq) error {
// 查询订单信息
var erpCoupon ErpCoupon
err := orm.Eloquent.Table("erp_coupon").Where("id=?", req.ErpCouponId).Find(&erpCoupon).Error
if err != nil {
logger.Error("purchase order err:", logger.Field("err", err))
return err
}
if erpCoupon.ID == 0 {
logger.Error("delete err, erpCoupon ID is:", logger.Field("erpCoupon.ID", req.ErpCouponId))
return errors.New(fmt.Sprintf("删除失败:未查询到优惠券id[%d]", req.ErpCouponId))
}
// 仅未开始和已结束的订单可删除
if erpCoupon.State != ErpCouponUnStarted && erpCoupon.State != ErpCouponFinished {
logger.Error("delete err, erpCoupon.State is:", logger.Field("erpCoupon.State", erpCoupon.State))
return errors.New("删除失败:仅未开始和已结束的优惠券可删除")
}
// 删除优惠订单
begin := orm.Eloquent.Begin()
err = begin.Delete(erpCoupon).Error
if err != nil {
begin.Rollback()
logger.Error("erp_coupon delete err:", logger.Field("err", err))
return err
}
err = begin.Table("coupon").Where("erp_coupon_id", req.ErpCouponId).Delete(&Coupon{}).Error
if err != nil {
begin.Rollback()
logger.Error("coupon delete err:", logger.Field("err", err))
return err
}
err = begin.Commit().Error
if err != nil {
begin.Rollback()
logger.Error("commit erp_coupon delete err:", logger.Field("err", err))
return err
}
return nil
}
// StartCouponIssuanceTask 启动发放优惠券的任务
func StartCouponIssuanceTask(req *ErpMarketingCouponStartReq, taskList []CouponIssuanceTask) error {
var erpCouponList []ErpCoupon
err := orm.Eloquent.Table("erp_coupon").Where("id in ?", req.ErpCouponId).Find(&erpCouponList).Error
if err != nil {
logger.Error("purchase order err:", logger.Field("err", err))
return err
}
if len(taskList) < len(erpCouponList) {
for _, erpCoupon := range erpCouponList {
exitFlag := false
for _, task := range taskList {
if erpCoupon.ID == task.ErpCouponId {
exitFlag = true
break
} else {
continue
}
}
if !exitFlag {
// 新建任务
taskInfo := CouponIssuanceTask{
ErpCouponId: erpCoupon.ID,
ErpCouponName: erpCoupon.Name,
Status: TaskInProgress,
LastUserID: 0,
ErrorMessage: "",
}
err := orm.Eloquent.Create(&taskInfo).Error
if err != nil {
logger.Error("create CouponIssuanceTask err:", logger.Field("err", err))
return err
}
taskList = append(taskList, taskInfo)
}
}
}
// 遍历taskList获取最小的lastUserID
var lastUserID uint32
for _, task := range taskList {
if lastUserID == 0 {
lastUserID = task.LastUserID
}
if lastUserID > task.LastUserID {
lastUserID = task.LastUserID
}
}
// 处理任务从上次中断的用户ID开始
for {
// 查询用户ID大于 lastUserID 的用户
users, err := getUsersAfterID(lastUserID)
if err != nil || len(users) == 0 {
log.Println("没有更多用户,任务结束")
break
}
// 发放优惠券
err = issueCouponsToUsers(users, req.ErpCouponId)
if err != nil {
log.Printf("发放优惠券失败: %v", err)
return err
}
// 更新任务进度记录处理到的最后一个用户ID
lastUserID = users[len(users)-1].ID
//err = updateTaskProgress(req.ErpCouponId, lastUserID, err.Error())
//if err != nil {
// log.Printf("更新任务进度失败: %v", err)
// break
//}
}
// 任务完成,更新状态
err = markTaskAsCompleted(req.ErpCouponId)
if err != nil {
return nil
}
return nil
}
// CheckUserType 校验批量选择的优惠券使用人群是否相同
func CheckUserType(erpCouponId []uint32) bool {
var erpCouponList []ErpCoupon
err := orm.Eloquent.Table("erp_coupon").Where("id in ?", erpCouponId).Find(&erpCouponList).Error
if err != nil || err == RecordNotFound {
logger.Error("query erp_coupon err:", logger.Field("err", err))
return false
}
var userType uint32
for _, erpCoupon := range erpCouponList {
if userType == 0 {
userType = erpCoupon.UserType
}
if userType != erpCoupon.UserType {
return false
}
}
return true
}
// GetTaskProgress 查询优惠券任务执行情况
func GetTaskProgress(erpCouponId []uint32) ([]CouponIssuanceTask, error) {
var task []CouponIssuanceTask
err := orm.Eloquent.Table("coupon_issuance_task").Where("erp_coupon_id in ?", erpCouponId).
Order("updated_at desc").First(&task).Error
if err != nil {
return nil, err
}
return task, nil
}
func getUsersAfterID(lastUserID uint32) ([]UserInfo, error) {
var users []UserInfo
err := orm.Eloquent.Model(&UserInfo{}).Where("id > ? ", lastUserID).
Order("id asc").Limit(100).Find(&users).Error
if err != nil {
return nil, err
}
return users, nil
}
func updateTaskProgress(erpCouponId []uint32, lastUserID uint32, errMsg string) error {
err := orm.Eloquent.Table("coupon_issuance_task").Where("erp_coupon_id in ?", erpCouponId).
Updates(map[string]interface{}{
"last_user_id": lastUserID,
"error_message": errMsg,
"updated_at": time.Now(),
}).Error
if err != nil {
return err
}
return nil
}
func markTaskAsCompleted(erpCouponId []uint32) error {
err := orm.Eloquent.Table("coupon_issuance_task").Where("erp_coupon_id in ?", erpCouponId).
Updates(map[string]interface{}{
"Status": TaskCompleted,
"updated_at": time.Now(),
}).Error
if err != nil {
log.Printf("更新任务完成状态失败: %v", err)
return err
}
log.Println("任务完成")
return nil
}
// 校验用户是否符合领取优惠券的条件
func isEligibleForCoupon(user UserInfo, erpCoupon ErpCoupon) bool {
switch erpCoupon.UserType {
case 1:
// 1-所有人:不限制会员等级,所有人均可领取
return true
case 2:
// 2-未付费用户MemberLevel 不能是 (2, 3, 4, 5)
if user.MemberLevel == MemberLevelGold ||
user.MemberLevel == MemberLevelPeriod ||
user.MemberLevel == MemberLevelPlatinum ||
user.MemberLevel == MemberLevelBlackGold {
return false
}
return true
case 3:
// 3-已付费用户MemberLevel 必须是 (2, 3, 4, 5)
if user.MemberLevel == MemberLevelGold ||
user.MemberLevel == MemberLevelPeriod ||
user.MemberLevel == MemberLevelPlatinum ||
user.MemberLevel == MemberLevelBlackGold {
return true
}
return false
default:
// 未知类型,不发放
return false
}
}
func issueCouponsToUsers(users []UserInfo, erpCouponId []uint32) error {
var erpCoupon []ErpCoupon
err := orm.Eloquent.Table("erp_coupon").Where("id in ?", erpCouponId).Find(&erpCoupon).Error
if err != nil || err == RecordNotFound {
logger.Error("query erp_coupon err:", logger.Field("err", err))
return err
}
for _, user := range users {
// 根据条件发放优惠券
if isEligibleForCoupon(user, erpCoupon[0]) {
// 发放优惠券
var coupons []Coupon
err = orm.Eloquent.Table("coupon").Where("erp_coupon_id in ?", erpCouponId).First(&coupons).Error
if err != nil {
logger.Error("query coupon err:", logger.Field("err", err))
return err
}
for _, coupon := range coupons {
couponCode, _ := utils.GenerateRandomNumber19()
userCoupon := &UserCoupon{
Uid: user.Uid,
CouponId: coupon.ID,
CouponType: coupon.CouponType,
Value: coupon.Value,
State: 1,
ActiveStart: time.Now(),
ActiveEnd: time.Now().AddDate(0, 0, 7),
RedeemCode: "",
CategoryNumber: coupon.CategoryNumber,
Code: couponCode,
}
err = orm.Eloquent.Create(userCoupon).Error
if err != nil {
logger.Error("create user coupon err:", logger.Field("err", err))
err = updateTaskProgress(erpCouponId, user.ID, err.Error())
if err != nil {
log.Printf("更新任务进度失败: %v", err)
}
return err
}
}
// 发送短信或者消息订阅通知
err = GtSendMessage([]string{user.Tel}, erpCoupon[0].SmsContent)
if err != nil {
logger.Error(err.Error())
}
}
}
return nil
}