预警2
This commit is contained in:
parent
d091564610
commit
31fb6e77da
|
|
@ -13,6 +13,10 @@ type WarningBudget struct {
|
|||
UsedBudget int64 // 已使用预算
|
||||
AvailableStock int64 // 可用库存
|
||||
RemainingBudget int64 // 剩余预算
|
||||
StockUsageRate float64 // 券使用率
|
||||
|
||||
StartTime *time.Time
|
||||
EndTime *time.Time
|
||||
}
|
||||
|
||||
type WarningBudgetLog struct {
|
||||
|
|
@ -20,3 +24,13 @@ type WarningBudgetLog struct {
|
|||
Num int
|
||||
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"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package repo
|
|||
import (
|
||||
"context"
|
||||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/biz/do"
|
||||
)
|
||||
|
||||
type ProductRepo interface {
|
||||
FindWarningBudget(ctx context.Context, fun func(ctx context.Context, rows []*bo.ProductBo) error) error
|
||||
GetByBatchNo(ctx context.Context, batchNo 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
|
||||
}
|
||||
|
||||
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
|
||||
couponAmount := *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount / 100
|
||||
|
||||
remainingBudget := availableStock * couponAmount
|
||||
|
||||
stockUsageRate := float64(*wxResp.DistributedCoupons) / float64(*wxResp.StockUseRule.MaxCoupons) * 100
|
||||
|
||||
req := &do.WarningBudget{
|
||||
StockName: product.Name,
|
||||
StockId: product.BatchNo,
|
||||
|
|
@ -156,37 +158,55 @@ func (v *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxRes
|
|||
UsedBudget: *wxResp.DistributedCoupons * couponAmount,
|
||||
AvailableStock: availableStock,
|
||||
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 {
|
||||
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 {
|
||||
return v.WarningSend(ctx, str)
|
||||
return v.WarningSend(ctx, formatAsCard(req))
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
func (v *VoucherBiz) WarningSend(ctx context.Context, str string) error {
|
||||
|
||||
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("\n")
|
||||
|
||||
// 使用率
|
||||
stockUsageRate := float64(req.UsedStock) / float64(req.AllStock) * 100
|
||||
|
||||
card.WriteString("#### 📈 使用率\n")
|
||||
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", stockUsageRate))
|
||||
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", req.StockUsageRate))
|
||||
|
||||
return card.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,20 @@ func NewDingMixRepoImpl(bc *conf.Bootstrap) mixrepos.DingMixRepo {
|
|||
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 {
|
||||
|
||||
isAtAll := false
|
||||
|
|
|
|||
|
|
@ -31,6 +31,6 @@ func NewSmsMixRepoImpl(bc *conf.Bootstrap) (mixrepos.SmsMixRepo, error) {
|
|||
return &SmsMixRepoImpl{bc: bc, smsService: smsService}, nil
|
||||
}
|
||||
|
||||
func (s *SmsMixRepoImpl) SendCode(ctx context.Context, phone, code string) error {
|
||||
return s.smsService.SendSMS(ctx, []string{phone}, s.bc.AliYunSms.TemplateSendCode, map[string]string{"code": code})
|
||||
func (s *SmsMixRepoImpl) Send(ctx context.Context, phoneNumbers []string, params map[string]string) error {
|
||||
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:支付宝
|
||||
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"`
|
||||
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不做预警
|
||||
WarningPerson string `gorm:"column:warning_person" json:"warning_person"`
|
||||
StartTime *time.Time `gorm:"column:start_time;not null" json:"start_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"`
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
err2 "voucher/api/err"
|
||||
"voucher/internal/biz/bo"
|
||||
"voucher/internal/biz/do"
|
||||
"voucher/internal/biz/repo"
|
||||
"voucher/internal/biz/vo"
|
||||
"voucher/internal/data"
|
||||
|
|
@ -37,9 +38,10 @@ func (r *ProductRepoImpl) FindWarningBudget(ctx context.Context, fun func(ctx co
|
|||
nowTime := time.Now().Format(time.DateTime)
|
||||
|
||||
result := r.db.DB(ctx).
|
||||
Where("warning_budget > 0").
|
||||
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 {
|
||||
return fun(ctx, r.ToBos(results))
|
||||
})
|
||||
|
|
@ -51,6 +53,36 @@ func (r *ProductRepoImpl) FindWarningBudget(ctx context.Context, fun func(ctx co
|
|||
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) {
|
||||
|
||||
var item *model.Product
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package sms
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"voucher/internal/biz/do"
|
||||
)
|
||||
|
||||
func TestSendSMS(t *testing.T) {
|
||||
|
||||
config := Config{
|
||||
var config = Config{
|
||||
AccessKeyID: "LTAI5tM1X4HuqUwT8D74qXAH",
|
||||
AccessKeySecret: "gxsC1QK12NSKH1HkCqKR1EnMdAy3ad",
|
||||
Endpoint: "dysmsapi.aliyuncs.com",
|
||||
|
|
@ -16,6 +18,8 @@ func TestSendSMS(t *testing.T) {
|
|||
Timeout: 15,
|
||||
}
|
||||
|
||||
func TestSendSMS(t *testing.T) {
|
||||
|
||||
smsService, err := NewService(config)
|
||||
if err != nil {
|
||||
t.Errorf("创建短信服务失败: %v", err)
|
||||
|
|
@ -37,3 +41,51 @@ func TestSendSMS(t *testing.T) {
|
|||
|
||||
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