预警2
This commit is contained in:
parent
d091564610
commit
31fb6e77da
|
|
@ -13,6 +13,10 @@ type WarningBudget struct {
|
||||||
UsedBudget int64 // 已使用预算
|
UsedBudget int64 // 已使用预算
|
||||||
AvailableStock int64 // 可用库存
|
AvailableStock int64 // 可用库存
|
||||||
RemainingBudget int64 // 剩余预算
|
RemainingBudget int64 // 剩余预算
|
||||||
|
StockUsageRate float64 // 券使用率
|
||||||
|
|
||||||
|
StartTime *time.Time
|
||||||
|
EndTime *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarningBudgetLog struct {
|
type WarningBudgetLog struct {
|
||||||
|
|
@ -20,3 +24,13 @@ type WarningBudgetLog struct {
|
||||||
Num int
|
Num int
|
||||||
LastTime time.Time
|
LastTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WarningPerson struct {
|
||||||
|
Mobile string `json:"mobile"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WarningSend struct {
|
||||||
|
title, text string
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@ package mixrepos
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
type SmsMixRepo interface {
|
type SmsMixRepo interface {
|
||||||
SendCode(ctx context.Context, phone, code string) error
|
Send(ctx context.Context, phoneNumbers []string, params map[string]string) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,21 @@ func (this *VoucherBiz) RegisterTag(ctx context.Context, batchNo string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxResp, err := this.WechatCpnRepo.QueryProduct(ctx, stock.MchId, stock.BatchNo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := this.GetWarningBudget(stock, wxResp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = this.ProductRepo.UpdateWarningBudget(ctx, stock.ID, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err = this.registerNotifyTag(ctx, stock.MchId, stock.BatchNo); err != nil {
|
if err = this.registerNotifyTag(ctx, stock.MchId, stock.BatchNo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ package repo
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"voucher/internal/biz/bo"
|
"voucher/internal/biz/bo"
|
||||||
|
"voucher/internal/biz/do"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProductRepo interface {
|
type ProductRepo interface {
|
||||||
FindWarningBudget(ctx context.Context, fun func(ctx context.Context, rows []*bo.ProductBo) error) error
|
FindWarningBudget(ctx context.Context, fun func(ctx context.Context, rows []*bo.ProductBo) error) error
|
||||||
GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error)
|
GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error)
|
||||||
GetByProductNo(ctx context.Context, productNo string) (*bo.ProductBo, error)
|
GetByProductNo(ctx context.Context, productNo string) (*bo.ProductBo, error)
|
||||||
|
UpdateWarningBudget(ctx context.Context, id int32, req *do.WarningBudget) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,13 +138,15 @@ func (v *VoucherBiz) warningBudget(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxResp *cashcoupons.Stock) error {
|
func (v *VoucherBiz) GetWarningBudget(product *bo.ProductBo, wxResp *cashcoupons.Stock) (*do.WarningBudget, error) {
|
||||||
|
|
||||||
availableStock := *wxResp.StockUseRule.MaxCoupons - *wxResp.DistributedCoupons
|
availableStock := *wxResp.StockUseRule.MaxCoupons - *wxResp.DistributedCoupons
|
||||||
couponAmount := *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount / 100
|
couponAmount := *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount / 100
|
||||||
|
|
||||||
remainingBudget := availableStock * couponAmount
|
remainingBudget := availableStock * couponAmount
|
||||||
|
|
||||||
|
stockUsageRate := float64(*wxResp.DistributedCoupons) / float64(*wxResp.StockUseRule.MaxCoupons) * 100
|
||||||
|
|
||||||
req := &do.WarningBudget{
|
req := &do.WarningBudget{
|
||||||
StockName: product.Name,
|
StockName: product.Name,
|
||||||
StockId: product.BatchNo,
|
StockId: product.BatchNo,
|
||||||
|
|
@ -156,37 +158,55 @@ func (v *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxRes
|
||||||
UsedBudget: *wxResp.DistributedCoupons * couponAmount,
|
UsedBudget: *wxResp.DistributedCoupons * couponAmount,
|
||||||
AvailableStock: availableStock,
|
AvailableStock: availableStock,
|
||||||
RemainingBudget: remainingBudget,
|
RemainingBudget: remainingBudget,
|
||||||
|
StockUsageRate: stockUsageRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
str := formatAsCard(req)
|
inputFormat := time.RFC3339
|
||||||
|
|
||||||
log.Warnf("预警查询,券预算明细,%s", str)
|
if wxResp.AvailableBeginTime != nil {
|
||||||
|
availableBeginTime, _ := time.Parse(inputFormat, *wxResp.AvailableBeginTime)
|
||||||
|
req.StartTime = &availableBeginTime
|
||||||
|
}
|
||||||
|
|
||||||
if product.WarningBudget >= remainingBudget {
|
if wxResp.AvailableEndTime != nil {
|
||||||
|
availableEndTime, _ := time.Parse(inputFormat, *wxResp.AvailableEndTime)
|
||||||
|
req.EndTime = &availableEndTime
|
||||||
|
}
|
||||||
|
|
||||||
count, err := v.WarningBudgetIncr(ctx, req.StockId)
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxResp *cashcoupons.Stock) error {
|
||||||
|
|
||||||
|
req, err := v.GetWarningBudget(product, wxResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = v.ProductRepo.UpdateWarningBudget(ctx, product.ID, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if product.WarningBudget >= req.RemainingBudget {
|
||||||
|
|
||||||
|
count, err2 := v.WarningBudgetIncr(ctx, req.StockId)
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
if count == 1 {
|
if count == 1 {
|
||||||
return v.WarningSend(ctx, str)
|
return v.WarningSend(ctx, formatAsCard(req))
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("预警查询,当前达到预警第[%d]次,暂不做通知", count)
|
log.Warnf("预警查询,当前达到预警第[%d]次,暂不做通知", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
//w := v.WarningBudgetAdd(req)
|
|
||||||
//if w.Num == 1 {
|
|
||||||
// return v.WarningSend(ctx, str)
|
|
||||||
//} else if w.Num > 5 {
|
|
||||||
// v.WarningBudgetRemove(req.StockId)
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VoucherBiz) WarningSend(ctx context.Context, str string) error {
|
func (v *VoucherBiz) WarningSend(ctx context.Context, str string) error {
|
||||||
|
|
||||||
return v.DingMixRepo.SendMarkdownMessage(ctx, "券预算不足", str)
|
return v.DingMixRepo.SendMarkdownMessage(ctx, "券预算不足", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,11 +232,8 @@ func formatAsCard(req *do.WarningBudget) string {
|
||||||
card.WriteString(fmt.Sprintf("- **剩余预算**: %d元\n", req.RemainingBudget))
|
card.WriteString(fmt.Sprintf("- **剩余预算**: %d元\n", req.RemainingBudget))
|
||||||
card.WriteString("\n")
|
card.WriteString("\n")
|
||||||
|
|
||||||
// 使用率
|
|
||||||
stockUsageRate := float64(req.UsedStock) / float64(req.AllStock) * 100
|
|
||||||
|
|
||||||
card.WriteString("#### 📈 使用率\n")
|
card.WriteString("#### 📈 使用率\n")
|
||||||
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", stockUsageRate))
|
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", req.StockUsageRate))
|
||||||
|
|
||||||
return card.String()
|
return card.String()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,20 @@ func NewDingMixRepoImpl(bc *conf.Bootstrap) mixrepos.DingMixRepo {
|
||||||
return &DingMixRepoImpl{bc: bc, client: client}
|
return &DingMixRepoImpl{bc: bc, client: client}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DingMixRepoImpl) SendMessage(_ context.Context, title, text string) error {
|
||||||
|
|
||||||
|
isAtAll := false
|
||||||
|
if len(s.bc.Alarm.AtMobiles) == 0 {
|
||||||
|
isAtAll = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.client.SendMarkdownMessage(title, text, s.bc.Alarm.AtMobiles, isAtAll); err != nil {
|
||||||
|
return fmt.Errorf("markdown 消息发送失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DingMixRepoImpl) SendMarkdownMessage(_ context.Context, title, text string) error {
|
func (s *DingMixRepoImpl) SendMarkdownMessage(_ context.Context, title, text string) error {
|
||||||
|
|
||||||
isAtAll := false
|
isAtAll := false
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,6 @@ func NewSmsMixRepoImpl(bc *conf.Bootstrap) (mixrepos.SmsMixRepo, error) {
|
||||||
return &SmsMixRepoImpl{bc: bc, smsService: smsService}, nil
|
return &SmsMixRepoImpl{bc: bc, smsService: smsService}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SmsMixRepoImpl) SendCode(ctx context.Context, phone, code string) error {
|
func (s *SmsMixRepoImpl) Send(ctx context.Context, phoneNumbers []string, params map[string]string) error {
|
||||||
return s.smsService.SendSMS(ctx, []string{phone}, s.bc.AliYunSms.TemplateSendCode, map[string]string{"code": code})
|
return s.smsService.SendSMS(ctx, phoneNumbers, s.bc.AliYunSms.TemplateSendCode, params)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,10 @@ type Product struct {
|
||||||
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
|
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
|
||||||
AvailableType uint8 `gorm:"column:available_type;not null;comment:1:固定有效期 2:动态有效期" json:"available_type"`
|
AvailableType uint8 `gorm:"column:available_type;not null;comment:1:固定有效期 2:动态有效期" json:"available_type"`
|
||||||
AvailableDays uint32 `gorm:"column:available_days;not null;comment:领取后多少天内" json:"available_days"`
|
AvailableDays uint32 `gorm:"column:available_days;not null;comment:领取后多少天内" json:"available_days"`
|
||||||
|
AllBudget int64 `gorm:"column:all_budget;not null;default:0" json:"all_budget"`
|
||||||
|
RemainingBudget int64 `gorm:"column:remaining_budget;not null;default:0" json:"remaining_budget"`
|
||||||
WarningBudget int64 `gorm:"column:warning_budget;not null;default:0" json:"warning_budget"` // 预警预算=0不做预警
|
WarningBudget int64 `gorm:"column:warning_budget;not null;default:0" json:"warning_budget"` // 预警预算=0不做预警
|
||||||
|
WarningPerson string `gorm:"column:warning_person" json:"warning_person"`
|
||||||
StartTime *time.Time `gorm:"column:start_time;not null" json:"start_time"`
|
StartTime *time.Time `gorm:"column:start_time;not null" json:"start_time"`
|
||||||
EndTime *time.Time `gorm:"column:end_time;not null" json:"end_time"`
|
EndTime *time.Time `gorm:"column:end_time;not null" json:"end_time"`
|
||||||
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
|
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
err2 "voucher/api/err"
|
err2 "voucher/api/err"
|
||||||
"voucher/internal/biz/bo"
|
"voucher/internal/biz/bo"
|
||||||
|
"voucher/internal/biz/do"
|
||||||
"voucher/internal/biz/repo"
|
"voucher/internal/biz/repo"
|
||||||
"voucher/internal/biz/vo"
|
"voucher/internal/biz/vo"
|
||||||
"voucher/internal/data"
|
"voucher/internal/data"
|
||||||
|
|
@ -37,9 +38,10 @@ func (r *ProductRepoImpl) FindWarningBudget(ctx context.Context, fun func(ctx co
|
||||||
nowTime := time.Now().Format(time.DateTime)
|
nowTime := time.Now().Format(time.DateTime)
|
||||||
|
|
||||||
result := r.db.DB(ctx).
|
result := r.db.DB(ctx).
|
||||||
Where("warning_budget > 0").
|
|
||||||
Where("start_time <= ?", nowTime).
|
Where("start_time <= ?", nowTime).
|
||||||
Where("end_time >= ?", nowTime).
|
Where("start_time <= ?", nowTime).
|
||||||
|
Where("warning_budget > 0").
|
||||||
|
Where("warning_person IS NOT NULL").
|
||||||
FindInBatches(&results, 5, func(tx *gorm.DB, batch int) error {
|
FindInBatches(&results, 5, func(tx *gorm.DB, batch int) error {
|
||||||
return fun(ctx, r.ToBos(results))
|
return fun(ctx, r.ToBos(results))
|
||||||
})
|
})
|
||||||
|
|
@ -51,6 +53,36 @@ func (r *ProductRepoImpl) FindWarningBudget(ctx context.Context, fun func(ctx co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProductRepoImpl) UpdateWarningBudget(ctx context.Context, id int32, req *do.WarningBudget) error {
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
u := model.Product{
|
||||||
|
AllBudget: req.AllBudget,
|
||||||
|
RemainingBudget: req.RemainingBudget,
|
||||||
|
UpdateTime: &now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.StartTime != nil {
|
||||||
|
u.StartTime = req.StartTime
|
||||||
|
}
|
||||||
|
if req.EndTime != nil {
|
||||||
|
u.EndTime = req.EndTime
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := r.db.DB(ctx).
|
||||||
|
Where(model.Product{
|
||||||
|
ID: id,
|
||||||
|
}).
|
||||||
|
Updates(u)
|
||||||
|
|
||||||
|
if tx.Error != nil {
|
||||||
|
return fmt.Errorf("db fail %w", tx.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ProductRepoImpl) GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error) {
|
func (r *ProductRepoImpl) GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error) {
|
||||||
|
|
||||||
var item *model.Product
|
var item *model.Product
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ package sms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"voucher/internal/biz/do"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSendSMS(t *testing.T) {
|
var config = Config{
|
||||||
|
|
||||||
config := Config{
|
|
||||||
AccessKeyID: "LTAI5tM1X4HuqUwT8D74qXAH",
|
AccessKeyID: "LTAI5tM1X4HuqUwT8D74qXAH",
|
||||||
AccessKeySecret: "gxsC1QK12NSKH1HkCqKR1EnMdAy3ad",
|
AccessKeySecret: "gxsC1QK12NSKH1HkCqKR1EnMdAy3ad",
|
||||||
Endpoint: "dysmsapi.aliyuncs.com",
|
Endpoint: "dysmsapi.aliyuncs.com",
|
||||||
|
|
@ -16,6 +18,8 @@ func TestSendSMS(t *testing.T) {
|
||||||
Timeout: 15,
|
Timeout: 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSendSMS(t *testing.T) {
|
||||||
|
|
||||||
smsService, err := NewService(config)
|
smsService, err := NewService(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("创建短信服务失败: %v", err)
|
t.Errorf("创建短信服务失败: %v", err)
|
||||||
|
|
@ -37,3 +41,51 @@ func TestSendSMS(t *testing.T) {
|
||||||
|
|
||||||
t.Logf("已发送至 %s\n", phoneNumber)
|
t.Logf("已发送至 %s\n", phoneNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWarningSend(t *testing.T) {
|
||||||
|
req := do.WarningBudget{
|
||||||
|
StockName: "招行2元立减金",
|
||||||
|
StockId: "20627186",
|
||||||
|
StockNo: "CMB20627186",
|
||||||
|
Amount: 2,
|
||||||
|
AllBudget: 70010,
|
||||||
|
AllStock: 35005,
|
||||||
|
UsedStock: 9390,
|
||||||
|
UsedBudget: 18780,
|
||||||
|
AvailableStock: 25615,
|
||||||
|
RemainingBudget: 51230,
|
||||||
|
StockUsageRate: 20,
|
||||||
|
}
|
||||||
|
params := buildTemplateParams(&req)
|
||||||
|
|
||||||
|
js, _ := json.Marshal(params)
|
||||||
|
|
||||||
|
t.Log(string(js))
|
||||||
|
|
||||||
|
smsService, err := NewService(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("创建短信服务失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = smsService.SendSMS(context.Background(), []string{"18666173766"}, "", params)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("发送短信失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTemplateParams(req *do.WarningBudget) map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"stock_name": req.StockName,
|
||||||
|
"stock_no": req.StockNo,
|
||||||
|
"amount": strconv.Itoa(int(req.Amount)),
|
||||||
|
"all_budget": strconv.Itoa(int(req.AllBudget)),
|
||||||
|
"all_stock": strconv.Itoa(int(req.AllStock)),
|
||||||
|
"used_stock": strconv.Itoa(int(req.UsedStock)),
|
||||||
|
"used_budget": strconv.Itoa(int(req.UsedBudget)),
|
||||||
|
"remaining_budget": strconv.Itoa(int(req.RemainingBudget)),
|
||||||
|
"remaining_stock": strconv.Itoa(int(req.AvailableStock)),
|
||||||
|
"budget_usage_rate": fmt.Sprintf("%.1f", req.StockUsageRate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue