From 40227538f938b324c95796d16b56e9b73dc6e315 Mon Sep 17 00:00:00 2001 From: chenlin Date: Fri, 29 Nov 2024 18:47:58 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E9=9B=B6=E5=94=AE=E4=BC=98=E6=83=A0?= =?UTF-8?q?=E5=88=B8=E5=8F=91=E6=94=BE=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9B=202=E3=80=81=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E8=AE=A2=E9=98=85=E9=80=9A=E7=9F=A5=E5=8F=91?= =?UTF-8?q?=E9=80=81=E9=80=BB=E8=BE=91=EF=BC=9B=203=E3=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=AE=9A=E6=97=B6=E5=88=B7=E6=96=B0=E5=88=B7=E6=96=B0?= =?UTF-8?q?access=5Ftoken=E9=80=BB=E8=BE=91=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/apis/market/marketing.go | 39 ++-- app/admin/models/greentown_sms.go | 19 +- app/admin/models/marketing.go | 290 ++++++++++++++++++++++++++--- cmd/api/server.go | 7 + docs/docs.go | 65 +++++-- docs/swagger.json | 65 +++++-- docs/swagger.yaml | 53 ++++-- static/导零售.xlsx | Bin 0 -> 11167 bytes 8 files changed, 436 insertions(+), 102 deletions(-) create mode 100644 static/导零售.xlsx diff --git a/app/admin/apis/market/marketing.go b/app/admin/apis/market/marketing.go index 11873bc..90b3db0 100644 --- a/app/admin/apis/market/marketing.go +++ b/app/admin/apis/market/marketing.go @@ -58,7 +58,7 @@ func ErpMarketingCouponCreate(c *gin.Context) { err := tools.Validate(req) //必填参数校验 if err != nil { - app.Error(c, http.StatusBadRequest, err, "参数错误:缺少必填参数") + app.Error(c, http.StatusBadRequest, err, err.Error()) return } @@ -96,7 +96,7 @@ func ErpMarketingCouponEdit(c *gin.Context) { err := tools.Validate(req) //必填参数校验 if err != nil { - app.Error(c, http.StatusBadRequest, err, "参数错误:缺少必填参数") + app.Error(c, http.StatusBadRequest, err, err.Error()) return } @@ -129,7 +129,7 @@ func ErpMarketingCouponDelete(c *gin.Context) { err := tools.Validate(req) //必填参数校验 if err != nil { - app.Error(c, http.StatusBadRequest, err, "参数错误:优惠券id为空") + app.Error(c, http.StatusBadRequest, err, err.Error()) return } @@ -162,7 +162,7 @@ func ErpMarketingCouponStart(c *gin.Context) { err := tools.Validate(req) //必填参数校验 if err != nil { - app.Error(c, http.StatusBadRequest, err, "参数错误:优惠券id为空") + app.Error(c, http.StatusBadRequest, err, err.Error()) return } @@ -174,18 +174,21 @@ func ErpMarketingCouponStart(c *gin.Context) { } var waitSendTaskList []model.CouponIssuanceTask - for _, task := range taskList { - switch task.Status { - case model.TaskInProgress: // 执行中 - app.Error(c, http.StatusInternalServerError, errors.New(fmt.Sprintf("[%s]已在执行中,请勿重复启动", task.ErpCouponName)), - fmt.Sprintf("[%s]已在执行中,请勿重复启动", task.ErpCouponName)) - return - case model.TaskCompleted: // 已完成 - app.Error(c, http.StatusInternalServerError, errors.New(fmt.Sprintf("[%s]已执行完成", task.ErpCouponName)), - fmt.Sprintf("[%s]已执行完成", task.ErpCouponName)) - return - default: - waitSendTaskList = append(waitSendTaskList, task) + + if len(taskList) != 0 { + for _, task := range taskList { + switch task.Status { + case model.TaskInProgress: // 执行中 + app.Error(c, http.StatusInternalServerError, errors.New(fmt.Sprintf("[%s]已在执行中,请勿重复启动", task.ErpCouponName)), + fmt.Sprintf("[%s]已在执行中,请勿重复启动", task.ErpCouponName)) + return + case model.TaskCompleted: // 已完成 + app.Error(c, http.StatusInternalServerError, errors.New(fmt.Sprintf("[%s]已执行完成", task.ErpCouponName)), + fmt.Sprintf("[%s]已执行完成", task.ErpCouponName)) + return + default: + waitSendTaskList = append(waitSendTaskList, task) + } } } @@ -195,9 +198,11 @@ func ErpMarketingCouponStart(c *gin.Context) { } // 更新短信和备注 - err = orm.Eloquent.Table("erp_coupon").Where("erp_coupon_id in ?", req.ErpCouponId). + err = orm.Eloquent.Table("erp_coupon").Where("id in ?", req.ErpCouponId). Updates(map[string]interface{}{ "remark": req.Remark, + "state": model.ErpCouponSending, + "start_time": time.Now(), "sms_content": req.SmsContent, "updated_at": time.Now(), }).Error diff --git a/app/admin/models/greentown_sms.go b/app/admin/models/greentown_sms.go index 203541e..7cf3df5 100644 --- a/app/admin/models/greentown_sms.go +++ b/app/admin/models/greentown_sms.go @@ -5,7 +5,6 @@ import ( "crypto/md5" "encoding/hex" "encoding/json" - "errors" "fmt" "go-admin/logger" "io" @@ -51,9 +50,9 @@ type GtSendMessageResp struct { } func GtSendMessage(phoneList []string, content string) error { - if len(phoneList) > GetSmsNumberRemaining() { // 待发送短信超出剩余可用数量 - return errors.New("短信剩余数量不足") - } + //if len(phoneList) > GetSmsNumberRemaining() { // 待发送短信超出剩余可用数量 + // return errors.New("短信剩余数量不足") + //} params := make(map[string]interface{}, 0) nowTime := time.Now() @@ -70,12 +69,12 @@ func GtSendMessage(phoneList []string, content string) error { } fmt.Println("resp:", resp) - // 更新已使用的短信数量 - err = UpdateSmsUsedCount(len(phoneList)) - if err != nil { - logger.Error("UpdateSmsUsedCount err", logger.Field("sms count", len(phoneList)), logger.Field("err", err)) - return err - } + //// 更新已使用的短信数量 + //err = UpdateSmsUsedCount(len(phoneList)) + //if err != nil { + // logger.Error("UpdateSmsUsedCount err", logger.Field("sms count", len(phoneList)), logger.Field("err", err)) + // return err + //} return nil } diff --git a/app/admin/models/marketing.go b/app/admin/models/marketing.go index 1292531..a11d302 100644 --- a/app/admin/models/marketing.go +++ b/app/admin/models/marketing.go @@ -1,12 +1,17 @@ package models import ( + "bytes" + "encoding/json" "errors" "fmt" utils "go-admin/app/admin/models/tools" orm "go-admin/common/global" "go-admin/logger" + "io/ioutil" "log" + "net/http" + "net/url" "time" ) @@ -20,8 +25,24 @@ const ( TaskInProgress = "in_progress" // 执行中 TaskCompleted = "completed" // 已完成 TaskFailed = "failed" // 发送失败 + + AccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token" + WXAppID = "wx806c079463b5b56c" + WXSecret = "cb125688bf4e482f66e8c46062d568fc" + + WxSubscribeMessage = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + TemplateId = "aP3c503T3wn-tJtpDccvxtNi7b1qjrwavFFfo-k2SAQ" + WxJumpUrl = "/pages/voucher/voucher" // 跳转微信小程序的页面路径 ) +// WxAccessToken 微信access_token +type WxAccessToken struct { + Model + Appid string `json:"appid"` // 小程序appid + AccessToken string `json:"access_token"` // 获取到的凭证 + ExpiresTime time.Time `json:"expires_time"` // 凭证有效时间 +} + // CouponIssuanceTask 优惠券发放任务表 type CouponIssuanceTask struct { Model @@ -35,21 +56,22 @@ type CouponIssuanceTask struct { // 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"` // 短信提示内容 + 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-仅限原价购买时使用 + StartTime *time.Time `json:"start_time"` // 启动时间 + 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 优惠券列表入参 @@ -75,7 +97,7 @@ type ErpMarketingCouponListResp struct { // ErpMarketingCouponCreateReq 新增优惠券入参 type ErpMarketingCouponCreateReq struct { Name string `json:"name" validate:"required"` // 优惠券名称 - Remark string `json:"remark"` // 名称备注 + 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"` // 可以使用该优惠券的商品分类编号,如果为空则表示没限制 @@ -187,7 +209,7 @@ func CreateErpMarketingCoupon(req *ErpMarketingCouponCreateReq) error { Rule: req.Rule, CategoryNumber: req.CategoryNumber, ActiveDate: req.ActiveDate, - Amount: req.Amount, + Amount: req.Amount * 100, UserType: req.UserType, Limit: req.Limit, State: ErpCouponUnStarted, @@ -204,9 +226,10 @@ func CreateErpMarketingCoupon(req *ErpMarketingCouponCreateReq) error { Describe: req.Describe, Rule: req.Rule, CouponType: "deduction", - Value: req.Amount, + Value: req.Amount * 100, CategoryNumber: req.CategoryNumber, ErpCouponId: erpCoupon.ID, + ActiveEnd: Now().AddDate(0, 0, int(req.ActiveDate)), } err = orm.Eloquent.Create(coupon).Error @@ -385,10 +408,11 @@ func StartCouponIssuanceTask(req *ErpMarketingCouponStartReq, taskList []CouponI } } + var nTotalCount int // 处理任务,从上次中断的用户ID开始 for { // 查询用户ID大于 lastUserID 的用户 - users, err := getUsersAfterID(lastUserID) + users, err := getUsersAfterID(lastUserID, erpCouponList[0].UserType) if err != nil || len(users) == 0 { log.Println("没有更多用户,任务结束") break @@ -403,6 +427,7 @@ func StartCouponIssuanceTask(req *ErpMarketingCouponStartReq, taskList []CouponI // 更新任务进度:记录处理到的最后一个用户ID lastUserID = users[len(users)-1].ID + nTotalCount += len(users) //err = updateTaskProgress(req.ErpCouponId, lastUserID, err.Error()) //if err != nil { // log.Printf("更新任务进度失败: %v", err) @@ -411,7 +436,7 @@ func StartCouponIssuanceTask(req *ErpMarketingCouponStartReq, taskList []CouponI } // 任务完成,更新状态 - err = markTaskAsCompleted(req.ErpCouponId) + err = markTaskAsCompleted(req.ErpCouponId, nTotalCount) if err != nil { return nil } @@ -447,17 +472,33 @@ 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 { + if err != nil && err.Error() != "record not found" { return nil, err } return task, nil } -func getUsersAfterID(lastUserID uint32) ([]UserInfo, error) { +func getUsersAfterID(lastUserID, userType uint32) ([]UserInfo, error) { + var err error var users []UserInfo - err := orm.Eloquent.Model(&UserInfo{}).Where("id > ? ", lastUserID). - Order("id asc").Limit(100).Find(&users).Error + + switch userType { + case 2: // 2-未付费用户:MemberLevel 不能是 (2, 3, 4, 5) + err = orm.Eloquent.Model(&UserInfo{}).Where("id > ? and member_level not in ?", lastUserID, []uint32{ + MemberLevelGold, MemberLevelPeriod, MemberLevelPlatinum, MemberLevelBlackGold}). + Order("id asc").Limit(100).Find(&users).Error + case 3: + // 3-已付费用户:MemberLevel 必须是 (2, 3, 4, 5) + err = orm.Eloquent.Model(&UserInfo{}).Where("id > ? and member_level in ?", lastUserID, []uint32{ + MemberLevelGold, MemberLevelPeriod, MemberLevelPlatinum, MemberLevelBlackGold}). + Order("id asc").Limit(100).Find(&users).Error + case 1: // 1-所有人:不限制会员等级,所有人均可领取 + default: + err = orm.Eloquent.Model(&UserInfo{}).Where("id > ?", lastUserID). + Order("id asc").Limit(100).Find(&users).Error + } + if err != nil { return nil, err } @@ -477,7 +518,7 @@ func updateTaskProgress(erpCouponId []uint32, lastUserID uint32, errMsg string) return nil } -func markTaskAsCompleted(erpCouponId []uint32) error { +func markTaskAsCompleted(erpCouponId []uint32, nTotalCount int) error { err := orm.Eloquent.Table("coupon_issuance_task").Where("erp_coupon_id in ?", erpCouponId). Updates(map[string]interface{}{ "Status": TaskCompleted, @@ -488,6 +529,17 @@ func markTaskAsCompleted(erpCouponId []uint32) error { return err } + err = orm.Eloquent.Table("erp_coupon").Where("id in ?", erpCouponId). + Updates(map[string]interface{}{ + "state": ErpCouponInProgress, + "send_count": nTotalCount, + "updated_at": time.Now(), + }).Error + if err != nil { + log.Printf("更新任务完成状态失败: %v", err) + return err + } + log.Println("任务完成") return nil } @@ -550,7 +602,7 @@ func issueCouponsToUsers(users []UserInfo, erpCouponId []uint32) error { Value: coupon.Value, State: 1, ActiveStart: time.Now(), - ActiveEnd: time.Now().AddDate(0, 0, 7), + ActiveEnd: coupon.ActiveEnd, RedeemCode: "", CategoryNumber: coupon.CategoryNumber, Code: couponCode, @@ -567,12 +619,192 @@ func issueCouponsToUsers(users []UserInfo, erpCouponId []uint32) error { } } - // 发送短信或者消息订阅通知 - err = GtSendMessage([]string{user.Tel}, erpCoupon[0].SmsContent) + // 发送订阅通知 + var wxToken WxAccessToken + err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error if err != nil { - logger.Error(err.Error()) + logger.Error("query wx_access_token err:", logger.Field("err", err)) + } + + if wxToken.ID == 0 || wxToken.ExpiresTime.Before(time.Now()) { + CheckAccessToken() + + err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error + if err != nil { + logger.Error("query wx_access_token err:", logger.Field("err", err)) + } + } + + toUserOpenid := user.WxOpenID + templateId := TemplateId + data := map[string]interface{}{ + "thing1": map[string]interface{}{"value": coupons[0].Name}, + "thing5": map[string]interface{}{"value": coupons[0].Describe}, + "date3": map[string]interface{}{"value": coupons[0].ActiveEnd.Format("2006年01月02日")}, + "thing4": map[string]interface{}{"value": coupons[0].Rule}, + } + //developer为开发版;trial为体验版;formal为正式版;默认为正式版 + _, err = SendMessage(wxToken.AccessToken, toUserOpenid, templateId, data, WxJumpUrl, "trial") + if err != nil { + // 如果订阅通知发送失败,则发送短信 + err = GtSendMessage([]string{user.Tel}, erpCoupon[0].SmsContent) + if err != nil { + logger.Error(err.Error()) + } } } } return nil } + +// Response 请求微信返回基础数据 +type Response struct { + Errcode int `json:"errcode"` + Errmsg string `json:"errmsg"` +} + +// AccessTokenResponse 返回给用户的数据 +type AccessTokenResponse struct { + AccessToken string `json:"access_token"` // 获取到的凭证 + ExpiresIn int64 `json:"expires_in"` // 凭证有效时间,单位:秒。目前是7200秒之内的值。 +} + +type accessTokenResponse struct { + Response + AccessTokenResponse +} + +// CheckAccessToken 定期更新access_token +func CheckAccessToken() { + accessToken, err := GetWxAccessToken() + if err != nil { + logger.Error("query wx_access_token err:", logger.Field("err", err)) + } else { + now := time.Now() + twoHoursLater := now.Add(2 * time.Hour) + + // 查询access_token + var wxToken WxAccessToken + err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID).First(&wxToken).Error + if err != nil { + logger.Error("query wx_access_token err:", logger.Field("err", err)) + } + + if wxToken.ID != 0 { // 更新 + // 更新access_token + err = orm.Eloquent.Table("wx_access_token").Where("appid = ?", WXAppID). + Updates(map[string]interface{}{ + "access_token": accessToken, + "expires_time": twoHoursLater, + }).Error + if err != nil { + logger.Errorf("更新wx_access_token失败: %v", err) + } + } else { // 插入 + tempToken := &WxAccessToken{ + Appid: WXAppID, + AccessToken: accessToken.AccessToken, + ExpiresTime: twoHoursLater, + } + err = orm.Eloquent.Create(tempToken).Error + if err != nil { + logger.Error("create wx_access_token err:", logger.Field("err", err)) + } + } + } +} + +// GetWxAccessToken 获取小程序的access_token +func GetWxAccessToken() (lures AccessTokenResponse, err error) { + api, err := code2url(WXAppID, WXSecret) + if err != nil { + return + } + + res, err := http.Get(api) + if err != nil { + return + } + defer res.Body.Close() + + if res.StatusCode != 200 { + err = errors.New("微信服务器发生错误") + return + } + + var data accessTokenResponse + err = json.NewDecoder(res.Body).Decode(&data) + if err != nil { + return + } + + if data.Errcode != 0 { + err = errors.New(data.Errmsg) + return + } + + lures = data.AccessTokenResponse + return +} + +// 拼接 获取 session_key 的 URL +func code2url(appID, secret string) (string, error) { + url, err := url.Parse(AccessTokenUrl) + if err != nil { + return "", err + } + + query := url.Query() + + query.Set("appid", appID) + query.Set("secret", secret) + query.Set("grant_type", "client_credential") + + url.RawQuery = query.Encode() + + return url.String(), nil +} + +// SendMessage 发送订阅消息 +func SendMessage(accessToken, toUserOpenid, templateId string, data interface{}, page, miniprogramState string) ( + []byte, error) { + url := fmt.Sprintf("%s%s", WxSubscribeMessage, accessToken) + body := map[string]interface{}{ + "touser": toUserOpenid, + "template_id": templateId, + "data": data, + "page": page, + "miniprogramState": miniprogramState, + "lang": "zh_CN", + } + + rsp, err := HttpRequest(http.MethodPost, url, body) + return rsp, err +} + +func HttpRequest(method, url string, reqBody interface{}) ([]byte, error) { + client := http.Client{} + reqBodyBytes, err := json.Marshal(reqBody) + if err != nil { + return nil, err + } + reqBodyReader := bytes.NewReader(reqBodyBytes) + req, err := http.NewRequest(method, url, reqBodyReader) + if err != nil { + return nil, err + } + + rsp, err := client.Do(req) + if err != nil { + return nil, err + } + defer rsp.Body.Close() + if rsp.StatusCode != 200 { + return nil, errors.New(fmt.Sprintf("response Status err:%d", rsp.StatusCode)) + } + rspBody, err := ioutil.ReadAll(rsp.Body) + if err != nil { + return nil, err + } + return rspBody, nil +} diff --git a/cmd/api/server.go b/cmd/api/server.go index fc35260..162651c 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -181,6 +181,13 @@ func run() error { if err != nil { fmt.Println("err:", err) } + + // 刷新access_token + err = s.Every(1).Hour().Do(models.CheckAccessToken) + if err != nil { + fmt.Println("err:", err) + } + <-s.Start() }() diff --git a/docs/docs.go b/docs/docs.go index efd1cdd..bf7e186 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -8982,20 +8982,20 @@ const docTemplate = `{ }, "amount": { "description": "金额(元)", - "type": "number" + "type": "integer" }, "category_number": { "description": "可以使用该优惠券的商品分类,如果为空则表示没限制", "type": "string" }, - "content": { - "description": "优惠内容", - "type": "string" - }, "createdAt": { "description": "创建时间", "type": "string" }, + "describe": { + "description": "优惠券简介", + "type": "string" + }, "id": { "description": "数据库记录编号", "type": "integer" @@ -9013,15 +9013,27 @@ const docTemplate = `{ "type": "number" }, "remark": { - "description": "名称备注", + "description": "备注", + "type": "string" + }, + "rule": { + "description": "优惠券使用规则", "type": "string" }, "send_count": { "description": "已发放", "type": "integer" }, + "sms_content": { + "description": "短信提示内容", + "type": "string" + }, + "start_time": { + "description": "启动时间", + "type": "string" + }, "state": { - "description": "当前状态 1-未开始;2-发放中;3-进行中;4-已结束", + "description": "当前状态 1-未开始;2-发放中;3-进行中;4-已结束;5-发放失败", "type": "integer" }, "total_pay_amount": { @@ -9595,14 +9607,14 @@ const docTemplate = `{ }, "amount": { "description": "金额(元)", - "type": "number" + "type": "integer" }, "category_number": { "description": "可以使用该优惠券的商品分类编号,如果为空则表示没限制", "type": "string" }, - "content": { - "description": "优惠内容/使用说明", + "describe": { + "description": "优惠券简介", "type": "string" }, "limit": { @@ -9614,7 +9626,11 @@ const docTemplate = `{ "type": "string" }, "remark": { - "description": "名称备注", + "description": "备注", + "type": "string" + }, + "rule": { + "description": "优惠券使用规则", "type": "string" }, "user_type": { @@ -9667,14 +9683,14 @@ const docTemplate = `{ }, "amount": { "description": "金额(元)", - "type": "number" + "type": "integer" }, "category_number": { "description": "可以使用该优惠券的商品分类,如果为空则表示没限制", "type": "string" }, - "content": { - "description": "优惠内容/使用说明", + "describe": { + "description": "优惠券简介", "type": "string" }, "erp_coupon_id": { @@ -9693,6 +9709,10 @@ const docTemplate = `{ "description": "名称备注", "type": "string" }, + "rule": { + "description": "优惠券使用规则", + "type": "string" + }, "user_type": { "description": "领取人限制:1-所有人 2-未付费用户 3-已付费用户 4-尊享会员", "type": "integer" @@ -9762,12 +9782,25 @@ const docTemplate = `{ "models.ErpMarketingCouponStartReq": { "type": "object", "required": [ - "erp_coupon_id" + "erp_coupon_id", + "remark", + "sms_content" ], "properties": { "erp_coupon_id": { "description": "优惠券id", - "type": "integer" + "type": "array", + "items": { + "type": "integer" + } + }, + "remark": { + "description": "活动名称备注", + "type": "string" + }, + "sms_content": { + "description": "短信提示内容", + "type": "string" } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 3750f37..b12a5fa 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -8971,20 +8971,20 @@ }, "amount": { "description": "金额(元)", - "type": "number" + "type": "integer" }, "category_number": { "description": "可以使用该优惠券的商品分类,如果为空则表示没限制", "type": "string" }, - "content": { - "description": "优惠内容", - "type": "string" - }, "createdAt": { "description": "创建时间", "type": "string" }, + "describe": { + "description": "优惠券简介", + "type": "string" + }, "id": { "description": "数据库记录编号", "type": "integer" @@ -9002,15 +9002,27 @@ "type": "number" }, "remark": { - "description": "名称备注", + "description": "备注", + "type": "string" + }, + "rule": { + "description": "优惠券使用规则", "type": "string" }, "send_count": { "description": "已发放", "type": "integer" }, + "sms_content": { + "description": "短信提示内容", + "type": "string" + }, + "start_time": { + "description": "启动时间", + "type": "string" + }, "state": { - "description": "当前状态 1-未开始;2-发放中;3-进行中;4-已结束", + "description": "当前状态 1-未开始;2-发放中;3-进行中;4-已结束;5-发放失败", "type": "integer" }, "total_pay_amount": { @@ -9584,14 +9596,14 @@ }, "amount": { "description": "金额(元)", - "type": "number" + "type": "integer" }, "category_number": { "description": "可以使用该优惠券的商品分类编号,如果为空则表示没限制", "type": "string" }, - "content": { - "description": "优惠内容/使用说明", + "describe": { + "description": "优惠券简介", "type": "string" }, "limit": { @@ -9603,7 +9615,11 @@ "type": "string" }, "remark": { - "description": "名称备注", + "description": "备注", + "type": "string" + }, + "rule": { + "description": "优惠券使用规则", "type": "string" }, "user_type": { @@ -9656,14 +9672,14 @@ }, "amount": { "description": "金额(元)", - "type": "number" + "type": "integer" }, "category_number": { "description": "可以使用该优惠券的商品分类,如果为空则表示没限制", "type": "string" }, - "content": { - "description": "优惠内容/使用说明", + "describe": { + "description": "优惠券简介", "type": "string" }, "erp_coupon_id": { @@ -9682,6 +9698,10 @@ "description": "名称备注", "type": "string" }, + "rule": { + "description": "优惠券使用规则", + "type": "string" + }, "user_type": { "description": "领取人限制:1-所有人 2-未付费用户 3-已付费用户 4-尊享会员", "type": "integer" @@ -9751,12 +9771,25 @@ "models.ErpMarketingCouponStartReq": { "type": "object", "required": [ - "erp_coupon_id" + "erp_coupon_id", + "remark", + "sms_content" ], "properties": { "erp_coupon_id": { "description": "优惠券id", - "type": "integer" + "type": "array", + "items": { + "type": "integer" + } + }, + "remark": { + "description": "活动名称备注", + "type": "string" + }, + "sms_content": { + "description": "短信提示内容", + "type": "string" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 99e31fb..e0e9e9b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1728,16 +1728,16 @@ definitions: type: integer amount: description: 金额(元) - type: number + type: integer category_number: description: 可以使用该优惠券的商品分类,如果为空则表示没限制 type: string - content: - description: 优惠内容 - type: string createdAt: description: 创建时间 type: string + describe: + description: 优惠券简介 + type: string id: description: 数据库记录编号 type: integer @@ -1751,13 +1751,22 @@ definitions: description: 客单价(元) type: number remark: - description: 名称备注 + description: 备注 + type: string + rule: + description: 优惠券使用规则 type: string send_count: description: 已发放 type: integer + sms_content: + description: 短信提示内容 + type: string + start_time: + description: 启动时间 + type: string state: - description: 当前状态 1-未开始;2-发放中;3-进行中;4-已结束 + description: 当前状态 1-未开始;2-发放中;3-进行中;4-已结束;5-发放失败 type: integer total_pay_amount: description: 支付金额(元) @@ -2172,12 +2181,12 @@ definitions: type: integer amount: description: 金额(元) - type: number + type: integer category_number: description: 可以使用该优惠券的商品分类编号,如果为空则表示没限制 type: string - content: - description: 优惠内容/使用说明 + describe: + description: 优惠券简介 type: string limit: description: 优惠券叠加限制 0-不限制;1-仅限原价购买时使用 @@ -2186,7 +2195,10 @@ definitions: description: 优惠券名称 type: string remark: - description: 名称备注 + description: 备注 + type: string + rule: + description: 优惠券使用规则 type: string user_type: description: 领取人限制:1-所有人 2-未付费用户 3-已付费用户 4-尊享会员 @@ -2223,12 +2235,12 @@ definitions: type: integer amount: description: 金额(元) - type: number + type: integer category_number: description: 可以使用该优惠券的商品分类,如果为空则表示没限制 type: string - content: - description: 优惠内容/使用说明 + describe: + description: 优惠券简介 type: string erp_coupon_id: description: 优惠券id @@ -2242,6 +2254,9 @@ definitions: remark: description: 名称备注 type: string + rule: + description: 优惠券使用规则 + type: string user_type: description: 领取人限制:1-所有人 2-未付费用户 3-已付费用户 4-尊享会员 type: integer @@ -2300,9 +2315,19 @@ definitions: properties: erp_coupon_id: description: 优惠券id - type: integer + items: + type: integer + type: array + remark: + description: 活动名称备注 + type: string + sms_content: + description: 短信提示内容 + type: string required: - erp_coupon_id + - remark + - sms_content type: object models.ErpOrder: properties: diff --git a/static/导零售.xlsx b/static/导零售.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8382a29887d2a5a30e5b1cfe19c1429a2d5343ac GIT binary patch literal 11167 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 Xs+44*e+EDtFdQ&l(7Vb=^@#gF-Hq=d literal 0 HcmV?d00001