From 2df24a5ff37c88d086f3e1f5b323a9fbcb99e271 Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Sun, 4 Jan 2026 17:16:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=94=80=E9=87=8F?= =?UTF-8?q?=E4=B8=8B=E6=BB=91=E6=98=8E=E7=BB=86=E5=88=86=E6=9E=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/ding_talk_bot.go | 5 +- internal/tools/bbxt/api.go | 22 ++--- internal/tools/bbxt/bbxt.go | 89 ++++++++++++++++- internal/tools/bbxt/bbxt_test.go | 13 +++ internal/tools/bbxt/entitys.go | 15 +++ internal/tools/bbxt/excel.go | 2 + internal/tools/bbxt/upload.go | 162 +++++++++++++++++++++++++++++++ tmpl/excel_temp/xlxhmx.xlsx | Bin 0 -> 10143 bytes 8 files changed, 290 insertions(+), 18 deletions(-) create mode 100644 tmpl/excel_temp/xlxhmx.xlsx diff --git a/internal/biz/ding_talk_bot.go b/internal/biz/ding_talk_bot.go index f1583e9..6788449 100644 --- a/internal/biz/ding_talk_bot.go +++ b/internal/biz/ding_talk_bot.go @@ -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 } diff --git a/internal/tools/bbxt/api.go b/internal/tools/bbxt/api.go index 8b6c823..a7b057b 100644 --- a/internal/tools/bbxt/api.go +++ b/internal/tools/bbxt/api.go @@ -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 销量同比分析 diff --git a/internal/tools/bbxt/bbxt.go b/internal/tools/bbxt/bbxt.go index 7c17166..27143dc 100644 --- a/internal/tools/bbxt/bbxt.go +++ b/internal/tools/bbxt/bbxt.go @@ -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 { diff --git a/internal/tools/bbxt/bbxt_test.go b/internal/tools/bbxt/bbxt_test.go index ca0fe68..3640dcd 100644 --- a/internal/tools/bbxt/bbxt_test.go +++ b/internal/tools/bbxt/bbxt_test.go @@ -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 { diff --git a/internal/tools/bbxt/entitys.go b/internal/tools/bbxt/entitys.go index d886dd6..b9bdf8f 100644 --- a/internal/tools/bbxt/entitys.go +++ b/internal/tools/bbxt/entitys.go @@ -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 //同比上周当前增减量 +} diff --git a/internal/tools/bbxt/excel.go b/internal/tools/bbxt/excel.go index c1034c4..19550cd 100644 --- a/internal/tools/bbxt/excel.go +++ b/internal/tools/bbxt/excel.go @@ -308,3 +308,5 @@ func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, d // 6. 保存 return f.SaveAs(outputPath) } + +// OfficialProductSumDeclineExcel diff --git a/internal/tools/bbxt/upload.go b/internal/tools/bbxt/upload.go index 90277e3..9eeb8a5 100644 --- a/internal/tools/bbxt/upload.go +++ b/internal/tools/bbxt/upload.go @@ -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) +} diff --git a/tmpl/excel_temp/xlxhmx.xlsx b/tmpl/excel_temp/xlxhmx.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fbfc23c16a89d665a14a2ba3980697ae0f5aaa18 GIT binary patch literal 10143 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 euUYH=5A1I(ilQv^&&7)Vr1^q