feat: 增加销量下滑明细分析功能
This commit is contained in:
parent
8271e7941d
commit
2df24a5ff3
|
|
@ -504,7 +504,6 @@ func (d *DingTalkBotBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
|
|||
if err != nil {
|
||||
log.Infof("时间识别失败:%s", configData.Time)
|
||||
entitys.ResText(rec.Ch, "", "时间识别失败了!可以给我一份比较具体的时间吗,例如“2025-12-31 12:00,抱歉抱歉😀")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -537,7 +536,7 @@ func (d *DingTalkBotBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
|
|||
reports = append(reports, repo)
|
||||
case "report_daily":
|
||||
product := strings.Split(group.ProductName, ",")
|
||||
repo, _err := rep.DailyReport(t, product, nil)
|
||||
repo, _err := rep.DailyReport(t, bbxt.DownWardValue, product, bbxt.SumFilter, nil)
|
||||
if _err != nil {
|
||||
return _err
|
||||
}
|
||||
|
|
@ -636,7 +635,7 @@ func (d *DingTalkBotBiz) GetReportLists(ctx context.Context, group *model.AiBotG
|
|||
product = strings.Split(group.ProductName, ",")
|
||||
}
|
||||
//[]string{"官方-爱奇艺-星钻季卡", "官方-爱奇艺-星钻半年卡", "官方--腾讯-年卡", "官方--爱奇艺-月卡"}
|
||||
reports, err = reportList.DailyReport(time.Now(), product, d.ossClient)
|
||||
reports, err = reportList.DailyReport(time.Now(), bbxt.DownWardValue, product, bbxt.SumFilter, d.ossClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,20 +113,20 @@ func GetStatisOfficialProductSumApi(param *GetStatisOfficialProductSumRequest) (
|
|||
}
|
||||
|
||||
type GetStatisOfficialProductSumDeclineResponse struct {
|
||||
OfficialProductSumDecline []*GetStatisOfficialProductSumDecline `protobuf:"bytes,1,rep,name=official_product_sum_decline,json=officialProductSumDecline,proto3" json:"official_product_sum_decline,omitempty"`
|
||||
DataCount int32 `protobuf:"varint,2,opt,name=data_count,json=dataCount,proto3" json:"data_count,omitempty"`
|
||||
OfficialProductSumDecline []*GetStatisOfficialProductSumDecline `protobuf:"bytes,1,rep,name=official_product_sum_decline,json=officialProductSumDecline,proto3" json:"officialProductSumDecline,omitempty"`
|
||||
DataCount int32 `protobuf:"varint,2,opt,name=data_count,json=dataCount,proto3" json:"dataCount,omitempty"`
|
||||
}
|
||||
|
||||
type GetStatisOfficialProductSumDecline struct {
|
||||
ResellerId int32 `protobuf:"varint,1,opt,name=reseller_id,json=resellerId,proto3" json:"reseller_id,omitempty"`
|
||||
OfficialProductId int32 `protobuf:"varint,2,opt,name=official_product_id,json=officialProductId,proto3" json:"official_product_id,omitempty"`
|
||||
OfficialProductName string `protobuf:"bytes,3,opt,name=official_product_name,json=officialProductName,proto3" json:"official_product_name,omitempty"`
|
||||
ResellerName string `protobuf:"bytes,4,opt,name=reseller_name,json=resellerName,proto3" json:"reseller_name,omitempty"`
|
||||
CurrentNum int32 `protobuf:"varint,5,opt,name=current_num,json=currentNum,proto3" json:"current_num,omitempty"`
|
||||
HistoryOneNum int32 `protobuf:"varint,6,opt,name=history_one_num,json=historyOneNum,proto3" json:"history_one_num,omitempty"`
|
||||
HistoryTwoNum int32 `protobuf:"varint,7,opt,name=history_two_num,json=historyTwoNum,proto3" json:"history_two_num,omitempty"`
|
||||
HistoryOneDiff int32 `protobuf:"varint,8,opt,name=history_one_diff,json=historyOneDiff,proto3" json:"history_one_diff,omitempty"`
|
||||
HistoryTwoDiff int32 `protobuf:"varint,9,opt,name=history_two_diff,json=historyTwoDiff,proto3" json:"history_two_diff,omitempty"`
|
||||
ResellerId int32 `protobuf:"varint,1,opt,name=reseller_id,json=resellerId,proto3" json:"resellerId,omitempty"`
|
||||
OfficialProductId int32 `protobuf:"varint,2,opt,name=official_product_id,json=officialProductId,proto3" json:"officialProductId,omitempty"`
|
||||
OfficialProductName string `protobuf:"bytes,3,opt,name=official_product_name,json=officialProductName,proto3" json:"officialProductName,omitempty"`
|
||||
ResellerName string `protobuf:"bytes,4,opt,name=reseller_name,json=resellerName,proto3" json:"resellerName,omitempty"`
|
||||
CurrentNum int32 `protobuf:"varint,5,opt,name=current_num,json=currentNum,proto3" json:"currentNum,omitempty"`
|
||||
HistoryOneNum int32 `protobuf:"varint,6,opt,name=history_one_num,json=historyOneNum,proto3" json:"historyOneNum,omitempty"`
|
||||
HistoryTwoNum int32 `protobuf:"varint,7,opt,name=history_two_num,json=historyTwoNum,proto3" json:"historyTwoNum,omitempty"`
|
||||
HistoryOneDiff int32 `protobuf:"varint,8,opt,name=history_one_diff,json=historyOneDiff,proto3" json:"historyOneDiff,omitempty"`
|
||||
HistoryTwoDiff int32 `protobuf:"varint,9,opt,name=history_two_diff,json=historyTwoDiff,proto3" json:"historyTwoDiff,omitempty"`
|
||||
}
|
||||
|
||||
// GetStatisOfficialProductSumDeclineApi 销量同比分析
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package bbxt
|
||||
|
||||
import (
|
||||
pkginner "ai_scheduler/internal/pkg"
|
||||
"ai_scheduler/internal/pkg/utils_oss"
|
||||
"ai_scheduler/pkg"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"slices"
|
||||
|
||||
"sort"
|
||||
|
||||
"time"
|
||||
|
|
@ -17,6 +17,11 @@ const (
|
|||
GreenStyle = "${color: 00B050;horizontal:center;vertical:center;borderColor:#000000}"
|
||||
)
|
||||
|
||||
var (
|
||||
DownWardValue int32 = 1500
|
||||
SumFilter int32 = -150
|
||||
)
|
||||
|
||||
var resellerBlackList = []string{
|
||||
"悦跑",
|
||||
"电商-独立",
|
||||
|
|
@ -46,7 +51,7 @@ func NewBbxtTools() (*BbxtTools, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (b *BbxtTools) DailyReport(now time.Time, productName []string, ossClient *utils_oss.Client) (reports []*ReportRes, err error) {
|
||||
func (b *BbxtTools) DailyReport(now time.Time, downWardValue int32, productName []string, sumFilter int32, ossClient *utils_oss.Client) (reports []*ReportRes, err error) {
|
||||
reports = make([]*ReportRes, 0, 4)
|
||||
productLossReport, err := b.StatisOursProductLossSum(now)
|
||||
if err != nil {
|
||||
|
|
@ -60,8 +65,12 @@ func (b *BbxtTools) DailyReport(now time.Time, productName []string, ossClient *
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
statisOfficialProductSumDecline, err := b.GetStatisOfficialProductSumDecline(now, downWardValue, productName, sumFilter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
reports = append(reports, productLossReport...)
|
||||
reports = append(reports, statisOfficialProductSum, profitRankingSum)
|
||||
reports = append(reports, statisOfficialProductSum, profitRankingSum, statisOfficialProductSumDecline)
|
||||
|
||||
if ossClient != nil {
|
||||
uploader := NewUploader(ossClient)
|
||||
|
|
@ -145,12 +154,12 @@ func (b *BbxtTools) StatisOursProductLossSum(now time.Time) (report []*ReportRes
|
|||
fmt.Sprintf("%s", v.ResellerName),
|
||||
fmt.Sprintf("%.2f", v.Total),
|
||||
})
|
||||
totalSum += v.Total
|
||||
}
|
||||
if v.Total <= -500 && !slices.Contains(resellerBlackList, v.ResellerName) {
|
||||
gt = append(gt, v)
|
||||
totalSum500 += v.Total
|
||||
}
|
||||
totalSum += v.Total
|
||||
}
|
||||
report = make([]*ReportRes, 2)
|
||||
timeCh := now.Format("1月2日15点")
|
||||
|
|
@ -314,6 +323,78 @@ func (b *BbxtTools) GetStatisOfficialProductSum(now time.Time, productName []str
|
|||
}, err
|
||||
}
|
||||
|
||||
// GetStatisOfficialProductSumDecline 销量下滑明细
|
||||
func (b *BbxtTools) GetStatisOfficialProductSumDecline(now time.Time, downWardValue int32, productName []string, sumFilter int32) (report *ReportRes, err error) {
|
||||
|
||||
var productMap = make(map[string]int)
|
||||
for k, v := range productName {
|
||||
productMap[v] = k
|
||||
}
|
||||
ct := []string{
|
||||
time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Format("2006-01-02 15:04:05"),
|
||||
adjustedTime(now),
|
||||
}
|
||||
var ids []int32
|
||||
if len(productName) > 0 {
|
||||
ids, err = b.getProductIdFromProductName(productName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
reqParam := &GetStatisOfficialProductSumRequest{
|
||||
Ct: ct,
|
||||
DownwardValue: downWardValue,
|
||||
OfficialProductId: ids,
|
||||
Page: 1,
|
||||
Limit: 1000,
|
||||
}
|
||||
|
||||
data, err := GetStatisOfficialProductSumDeclineApi(reqParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var (
|
||||
productSumMap = make(map[int32]ProductSumDecline)
|
||||
)
|
||||
for _, v := range data.OfficialProductSumDecline {
|
||||
if _, ex := productSumMap[v.OfficialProductId]; !ex {
|
||||
productSumMap[v.OfficialProductId] = ProductSumDecline{
|
||||
OfficialProductName: v.OfficialProductName,
|
||||
OfficialProductId: v.OfficialProductId,
|
||||
ProductSumReseller: make(map[int32]ProductSumReseller),
|
||||
}
|
||||
|
||||
}
|
||||
if v.HistoryOneDiff <= sumFilter || v.HistoryTwoDiff <= sumFilter {
|
||||
productSumMap[v.OfficialProductId].ProductSumReseller[v.ResellerId] = ProductSumReseller{
|
||||
ResellerName: v.ResellerName,
|
||||
CurrentNum: v.CurrentNum,
|
||||
HistoryOneNum: v.HistoryOneNum,
|
||||
HistoryOneDiff: v.HistoryOneDiff,
|
||||
HistoryTwoNum: v.HistoryTwoNum,
|
||||
HistoryTwoDiff: v.HistoryTwoDiff,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
timeCh := now.Format("1月2日15点")
|
||||
//title := "截至" + timeCh + "销量下滑大于" + fmt.Sprintf("%d", downWardValue) + "明细,分销商仅展示差额大于" + fmt.Sprintf("%d", -sumFilter)
|
||||
title := "截至" + timeCh + "点销量下滑较大商品"
|
||||
//总量生成excel
|
||||
if len(productSumMap) == 0 {
|
||||
return
|
||||
}
|
||||
filePath := b.cacheDir + "/xlxhmx" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
||||
err = b.OfficialProductSumDeclineExcel(b.excelTempDir+"/"+"/xlxhmx.xlsx", filePath, productSumMap, title)
|
||||
return &ReportRes{
|
||||
ReportName: "销售下滑明细",
|
||||
Title: title,
|
||||
Path: filePath,
|
||||
Desc: pkginner.JsonStringIgonErr(productSumMap),
|
||||
}, err
|
||||
}
|
||||
|
||||
func (b *BbxtTools) getProductIdFromProductName(productNames []string) ([]int32, error) {
|
||||
data, err := GetStatisFilterOfficialProductApi(&GetStatisFilterOfficialProductRequest{})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,19 @@ func Test_GetProfitRankingSum(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func Test_GetStatisOfficialProductSumDecline(t *testing.T) {
|
||||
o, err := NewBbxtTools()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s := "官方--美团外卖红包5元,官方--美团外卖红包10元,官方--饿了么超级会员月卡,官方--网易云黑胶vip月卡,官方--喜马拉雅巅峰会员月卡,官方--芒果-PC季卡,官方--芒果-PC月卡,官方--芒果-PC周卡,官方--腾讯-周卡,官方--优酷周卡,官方--QQ音乐-绿钻月卡,官方--爱奇艺-周卡,官方--腾讯-月卡,官方--腾讯-季卡,官方--腾讯-年卡,官方--优酷月卡,官方--优酷季卡,官方--优酷年卡,官方--爱奇艺-月卡,官方--爱奇艺-季卡,官方--爱奇艺-年卡"
|
||||
//s := "官方--QQ音乐-绿钻月卡"
|
||||
report, err := o.GetStatisOfficialProductSumDecline(time.Now(), 1000, strings.Split(s, ","), -150)
|
||||
|
||||
t.Log(report, err)
|
||||
|
||||
}
|
||||
|
||||
func Test_GetStatisOfficialProductSum(t *testing.T) {
|
||||
o, err := NewBbxtTools()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -21,3 +21,18 @@ type ReportRes struct {
|
|||
Data [][]string
|
||||
Desc string
|
||||
}
|
||||
|
||||
type ProductSumDecline struct {
|
||||
OfficialProductId int32
|
||||
OfficialProductName string
|
||||
ProductSumReseller map[int32]ProductSumReseller
|
||||
}
|
||||
|
||||
type ProductSumReseller struct {
|
||||
ResellerName string
|
||||
CurrentNum int32 //今日成功数量
|
||||
HistoryOneNum int32 //昨日成功数量
|
||||
HistoryOneDiff int32 //同比昨日当前增减量
|
||||
HistoryTwoNum int32 //上周成功数量
|
||||
HistoryTwoDiff int32 //同比上周当前增减量
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,3 +308,5 @@ func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, d
|
|||
// 6. 保存
|
||||
return f.SaveAs(outputPath)
|
||||
}
|
||||
|
||||
// OfficialProductSumDeclineExcel
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
|
|
@ -170,3 +172,163 @@ func (u *Uploader) uploadToOSS(fileName string, fileBytes []byte) string {
|
|||
// }
|
||||
// return url
|
||||
//}
|
||||
|
||||
func (b *BbxtTools) OfficialProductSumDeclineExcel(templatePath, outputPath string, sumMap map[int32]ProductSumDecline, title string) error {
|
||||
// 1. 读取模板
|
||||
f, err := excelize.OpenFile(templatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sheet := f.GetSheetName(0)
|
||||
if len(title) > 0 {
|
||||
// 写入标题
|
||||
f.SetCellValue(sheet, "A1", title)
|
||||
}
|
||||
// ---------------- 样式获取 ----------------
|
||||
// 模板第2行:数据行样式
|
||||
tplRowData := 3
|
||||
styleA2, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", tplRowData))
|
||||
if err != nil {
|
||||
styleA2 = 0
|
||||
}
|
||||
// B2和C2通常样式一致,这里取B2作为明细列样式
|
||||
styleB2, err := f.GetCellStyle(sheet, fmt.Sprintf("B%d", tplRowData))
|
||||
if err != nil {
|
||||
styleB2 = 0
|
||||
}
|
||||
styleC2, err := f.GetCellStyle(sheet, fmt.Sprintf("C%d", tplRowData))
|
||||
if err != nil {
|
||||
styleC2 = 0
|
||||
}
|
||||
styleD2, err := f.GetCellStyle(sheet, fmt.Sprintf("D%d", tplRowData))
|
||||
if err != nil {
|
||||
styleC2 = 0
|
||||
}
|
||||
//styleE2, err := f.GetCellStyle(sheet, fmt.Sprintf("E%d", tplRowData))
|
||||
//if err != nil {
|
||||
// styleC2 = 0
|
||||
//}
|
||||
styleF2, err := f.GetCellStyle(sheet, fmt.Sprintf("F%d", tplRowData))
|
||||
if err != nil {
|
||||
styleC2 = 0
|
||||
}
|
||||
//styleG2, err := f.GetCellStyle(sheet, fmt.Sprintf("G%d", tplRowData))
|
||||
//if err != nil {
|
||||
// styleC2 = 0
|
||||
//}
|
||||
|
||||
rowHeightData, err := f.GetRowHeight(sheet, tplRowData)
|
||||
if err != nil {
|
||||
rowHeightData = 20
|
||||
}
|
||||
|
||||
currentRow := 3
|
||||
pattern := `\$\{(.*?)\}`
|
||||
re := regexp.MustCompile(pattern)
|
||||
for _, product := range sumMap {
|
||||
// 排序 ProductLoss
|
||||
var reseller []ProductSumReseller
|
||||
for _, p := range product.ProductSumReseller {
|
||||
reseller = append(reseller, p)
|
||||
}
|
||||
sort.Slice(reseller, func(i, j int) bool {
|
||||
return reseller[i].HistoryOneDiff < reseller[j].HistoryOneDiff
|
||||
})
|
||||
|
||||
startRow := currentRow
|
||||
|
||||
// 填充该经销商的所有产品
|
||||
for _, p := range reseller {
|
||||
// 设置行高
|
||||
var (
|
||||
oneDiff string
|
||||
twoDiff string
|
||||
)
|
||||
f.SetRowHeight(sheet, currentRow, rowHeightData)
|
||||
if p.HistoryOneDiff >= 0 {
|
||||
oneDiff = fmt.Sprintf("%s↑%d", RedStyle, p.HistoryOneDiff)
|
||||
} else {
|
||||
oneDiff = fmt.Sprintf("%s↓%d", GreenStyle, p.HistoryOneDiff)
|
||||
}
|
||||
if p.HistoryTwoDiff >= 0 {
|
||||
twoDiff = fmt.Sprintf("%s↑%d", RedStyle, p.HistoryTwoDiff)
|
||||
} else {
|
||||
twoDiff = fmt.Sprintf("%s↓%d", GreenStyle, p.HistoryTwoDiff)
|
||||
}
|
||||
matchesE := re.FindStringSubmatch(oneDiff)
|
||||
styleMap := make(map[string]string)
|
||||
|
||||
if len(matchesE) != 2 {
|
||||
continue
|
||||
}
|
||||
for _, kv := range strings.Split(matchesE[1], ";") {
|
||||
kvParts := strings.Split(kv, ":")
|
||||
if len(kvParts) == 2 {
|
||||
styleMap[strings.TrimSpace(kvParts[0])] = strings.TrimSpace(kvParts[1])
|
||||
}
|
||||
}
|
||||
fontStyleIDE, _err := SetStyle(styleMap, f)
|
||||
if _err != nil {
|
||||
log.Errorf("set style failed: %v", _err)
|
||||
}
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("E%d", currentRow), fmt.Sprintf("E%d", currentRow), fontStyleIDE)
|
||||
oneDiffValue := re.ReplaceAllString(oneDiff, "")
|
||||
matchesG := re.FindStringSubmatch(twoDiff)
|
||||
styleMapG := make(map[string]string)
|
||||
if len(matchesG) != 2 {
|
||||
continue
|
||||
}
|
||||
for _, kv := range strings.Split(matchesG[1], ";") {
|
||||
kvParts := strings.Split(kv, ":")
|
||||
if len(kvParts) == 2 {
|
||||
styleMapG[strings.TrimSpace(kvParts[0])] = strings.TrimSpace(kvParts[1])
|
||||
}
|
||||
}
|
||||
fontStyleIDG, _err := SetStyle(styleMapG, f)
|
||||
if _err != nil {
|
||||
log.Errorf("set style failed: %v", _err)
|
||||
}
|
||||
twoDiffValue := re.ReplaceAllString(twoDiff, "")
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("G%d", currentRow), fmt.Sprintf("G%d", currentRow), fontStyleIDG)
|
||||
// 设置值
|
||||
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), product.OfficialProductName)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), p.ResellerName)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("C%d", currentRow), p.CurrentNum)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("D%d", currentRow), p.HistoryOneNum)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("E%d", currentRow), oneDiffValue)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("F%d", currentRow), p.HistoryTwoNum)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("G%d", currentRow), twoDiffValue)
|
||||
|
||||
// 设置样式
|
||||
if styleA2 != 0 {
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("A%d", currentRow), styleA2)
|
||||
}
|
||||
if styleB2 != 0 {
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("B%d", currentRow), fmt.Sprintf("B%d", currentRow), styleB2)
|
||||
}
|
||||
if styleC2 != 0 {
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("C%d", currentRow), fmt.Sprintf("C%d", currentRow), styleC2)
|
||||
}
|
||||
if styleD2 != 0 {
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("D%d", currentRow), fmt.Sprintf("D%d", currentRow), styleC2)
|
||||
}
|
||||
|
||||
if styleF2 != 0 {
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("F%d", currentRow), fmt.Sprintf("F%d", currentRow), styleC2)
|
||||
}
|
||||
|
||||
currentRow++
|
||||
}
|
||||
|
||||
endRow := currentRow - 1
|
||||
// 合并单元格 (如果多于1行)
|
||||
if endRow > startRow {
|
||||
f.MergeCell(sheet, fmt.Sprintf("A%d", startRow), fmt.Sprintf("A%d", endRow))
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 保存
|
||||
return f.SaveAs(outputPath)
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue