424 lines
12 KiB
Go
424 lines
12 KiB
Go
package bbxt
|
||
|
||
import (
|
||
pkginner "ai_scheduler/internal/pkg"
|
||
"ai_scheduler/internal/pkg/utils_oss"
|
||
"ai_scheduler/pkg"
|
||
"fmt"
|
||
"math/rand"
|
||
"slices"
|
||
"sort"
|
||
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
RedStyle = "${color: FF0000;horizontal:center;vertical:center;borderColor:#000000}"
|
||
GreenStyle = "${color: 00B050;horizontal:center;vertical:center;borderColor:#000000}"
|
||
)
|
||
|
||
var (
|
||
DownWardValue int32 = 1500
|
||
SumFilter int32 = -150
|
||
)
|
||
|
||
var resellerBlackList = []string{
|
||
"悦跑",
|
||
"电商-独立",
|
||
"蓝星严选连续包月",
|
||
"通钱-2025年12月",
|
||
}
|
||
|
||
type BbxtTools struct {
|
||
cacheDir string
|
||
excelTempDir string
|
||
ossClient *utils_oss.Client
|
||
}
|
||
|
||
func NewBbxtTools() (*BbxtTools, error) {
|
||
cache, err := pkg.GetCacheDir()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
tempDir, err := pkg.GetTmplDir()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &BbxtTools{
|
||
cacheDir: cache,
|
||
excelTempDir: fmt.Sprintf("%s/excel_temp", tempDir),
|
||
}, nil
|
||
}
|
||
|
||
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 {
|
||
return
|
||
}
|
||
profitRankingSum, err := b.GetProfitRankingSum(now)
|
||
if err != nil {
|
||
return
|
||
}
|
||
statisOfficialProductSum, err := b.GetStatisOfficialProductSum(now, productName)
|
||
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, statisOfficialProductSumDecline)
|
||
|
||
if ossClient != nil {
|
||
uploader := NewUploader(ossClient)
|
||
for _, report := range reports {
|
||
_ = uploader.Run(report)
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// StatisOursProductLossSum 负利润分析
|
||
func (b *BbxtTools) StatisOursProductLossSum(now time.Time) (report []*ReportRes, err error) {
|
||
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), //adjustedTime(time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())),
|
||
}
|
||
|
||
data, err := StatisOursProductLossSumApi(&StatisOursProductLossSumReq{
|
||
Ct: ct,
|
||
})
|
||
if err != nil {
|
||
return
|
||
}
|
||
var (
|
||
resellerMap = make(map[int32]*ResellerLoss)
|
||
total [][]string
|
||
gt []*ResellerLoss
|
||
)
|
||
|
||
for _, info := range data.List {
|
||
// 检查经销商是否已存在
|
||
if _, ok := resellerMap[info.ResellerId]; !ok {
|
||
// 创建新的经销商记录
|
||
resellerMap[info.ResellerId] = &ResellerLoss{
|
||
ResellerId: info.ResellerId,
|
||
ResellerName: info.ResellerName,
|
||
Total: 0, // 初始化为0,后续累加
|
||
ProductLoss: make(map[int32]ProductLoss), // 初始化map
|
||
}
|
||
}
|
||
|
||
// 获取当前经销商
|
||
reseller := resellerMap[info.ResellerId]
|
||
|
||
// 累加经销商总亏损
|
||
reseller.Total += info.Loss
|
||
|
||
// 检查产品是否已存在
|
||
if _, ok := reseller.ProductLoss[info.OursProductId]; !ok {
|
||
// 创建新的产品亏损记录
|
||
reseller.ProductLoss[info.OursProductId] = ProductLoss{
|
||
ProductId: info.OursProductId,
|
||
ProductName: info.OursProductName,
|
||
Loss: info.Loss, // 初始化为当前产品的亏损
|
||
}
|
||
} else {
|
||
// 已存在产品记录,累加亏损
|
||
productLoss := reseller.ProductLoss[info.OursProductId]
|
||
productLoss.Loss += info.Loss
|
||
reseller.ProductLoss[info.OursProductId] = productLoss
|
||
}
|
||
}
|
||
|
||
// 按经销商总亏损排序
|
||
resellers := make([]*ResellerLoss, 0, len(resellerMap))
|
||
for _, v := range resellerMap {
|
||
resellers = append(resellers, v)
|
||
}
|
||
sort.Slice(resellers, func(i, j int) bool {
|
||
return resellers[i].Total < resellers[j].Total
|
||
})
|
||
var (
|
||
totalSum float64
|
||
totalSum500 float64
|
||
)
|
||
// 构建分组
|
||
for _, v := range resellers {
|
||
if v.Total <= -100 && !slices.Contains(resellerBlackList, v.ResellerName) {
|
||
total = append(total, []string{
|
||
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
|
||
}
|
||
}
|
||
report = make([]*ReportRes, 2)
|
||
timeCh := now.Format("1月2日15点")
|
||
//总量生成excel
|
||
if len(total) > 0 {
|
||
filePath := b.cacheDir + "/kshj_total" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
||
err = b.SimpleFillExcelWithTitle(b.excelTempDir+"/"+"kshj_total.xlsx", filePath, total, "")
|
||
report[0] = &ReportRes{
|
||
ReportName: "负利润分析(合计表)",
|
||
Title: "截至" + timeCh + "利润累计亏损" + fmt.Sprintf("%.2f", totalSum),
|
||
Path: filePath,
|
||
Data: total,
|
||
}
|
||
}
|
||
|
||
if err != nil {
|
||
return
|
||
}
|
||
if len(gt) > 0 {
|
||
filePath := b.cacheDir + "/kshj_gt" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
||
title := "截至" + timeCh + "亏损500以上的分销商和产品"
|
||
err = b.resellerDetailFillExcelV2(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt, title)
|
||
report[1] = &ReportRes{
|
||
ReportName: "负利润分析(亏损500以上)",
|
||
Title: "截至" + timeCh + "亏损500以上利润累计亏损" + fmt.Sprintf("%.2f", totalSum500),
|
||
Path: filePath,
|
||
Data: total,
|
||
}
|
||
}
|
||
if err != nil {
|
||
return
|
||
}
|
||
return report, nil
|
||
}
|
||
|
||
// GetProfitRankingSum 利润同比分销商排行榜
|
||
func (b *BbxtTools) GetProfitRankingSum(now time.Time) (report *ReportRes, err error) {
|
||
|
||
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),
|
||
}
|
||
|
||
data, err := GetProfitRankingSumApi(&GetProfitRankingSumRequest{
|
||
Ct: ct,
|
||
})
|
||
timeCh := now.Format("1月2日15点")
|
||
title := "截至" + timeCh + "利润同比分销商排行榜"
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
//排序
|
||
sort.Slice(data.List, func(i, j int) bool {
|
||
return data.List[i].HistoryOneDiff > data.List[j].HistoryOneDiff
|
||
})
|
||
//取前20和后20
|
||
var (
|
||
total [][]string
|
||
top = data.List[:20]
|
||
bottom = data.List[len(data.List)-20:]
|
||
)
|
||
//合并前20和后20
|
||
top = append(top, bottom...)
|
||
|
||
// 构建分组
|
||
for _, v := range top {
|
||
var diff string
|
||
if v.HistoryOneDiff > 0 {
|
||
diff = fmt.Sprintf("%s↑%.4f", RedStyle, v.HistoryOneDiff)
|
||
} else {
|
||
diff = fmt.Sprintf("%s↓%.4f", GreenStyle, v.HistoryOneDiff)
|
||
}
|
||
total = append(total, []string{
|
||
fmt.Sprintf("%s", v.ResellerName),
|
||
fmt.Sprintf("%.4f", v.CurrentProfit),
|
||
fmt.Sprintf("%.4f", v.HistoryOneProfit),
|
||
diff,
|
||
})
|
||
}
|
||
//总量生成excel
|
||
if len(total) == 0 {
|
||
return
|
||
}
|
||
filePath := b.cacheDir + "/lrtb_rank" + fmt.Sprintf("%d", time.Now().Unix()) + ".xlsx"
|
||
err = b.SimpleFillExcelWithTitle(b.excelTempDir+"/"+"lrtb_rank.xlsx", filePath, total, title)
|
||
return &ReportRes{
|
||
ReportName: "利润同比分销商排行榜",
|
||
Title: title,
|
||
Path: filePath,
|
||
Data: total,
|
||
}, err
|
||
}
|
||
|
||
// GetStatisOfficialProductSum 销量同比分析
|
||
func (b *BbxtTools) GetStatisOfficialProductSum(now time.Time, productName []string) (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,
|
||
}
|
||
if len(ids) > 0 {
|
||
reqParam.OfficialProductId = ids
|
||
}
|
||
data, err := GetStatisOfficialProductSumApi(reqParam)
|
||
if err != nil {
|
||
return
|
||
}
|
||
var total = make([][]string, len(ids))
|
||
for _, v := range data.OfficialProductSum {
|
||
var (
|
||
yeterDatyDiff string
|
||
lastWeekDiff string
|
||
)
|
||
if v.HistoryOneDiff > 0 {
|
||
yeterDatyDiff = fmt.Sprintf("%s↑%d", RedStyle, v.HistoryOneDiff)
|
||
} else {
|
||
yeterDatyDiff = fmt.Sprintf("%s↓%d", GreenStyle, v.HistoryOneDiff)
|
||
}
|
||
if v.HistoryTwoDiff > 0 {
|
||
lastWeekDiff = fmt.Sprintf("%s↑%d", RedStyle, v.HistoryTwoDiff)
|
||
} else {
|
||
lastWeekDiff = fmt.Sprintf("%s↓%d", GreenStyle, v.HistoryTwoDiff)
|
||
}
|
||
total[productMap[v.OfficialProductName]] = []string{
|
||
fmt.Sprintf("%s", v.OfficialProductName),
|
||
fmt.Sprintf("%d", v.CurrentNum),
|
||
fmt.Sprintf("%d", v.HistoryOneNum),
|
||
yeterDatyDiff,
|
||
fmt.Sprintf("%d", v.HistoryTwoNum),
|
||
lastWeekDiff,
|
||
}
|
||
|
||
}
|
||
timeCh := now.Format("1月2日15点")
|
||
title := "截至" + timeCh + "销售同比分析"
|
||
//总量生成excel
|
||
if len(total) == 0 {
|
||
return
|
||
}
|
||
filePath := b.cacheDir + "/xstb_ana" + fmt.Sprintf("%d", time.Now().Unix()) + ".xlsx"
|
||
err = b.SimpleFillExcelWithTitle(b.excelTempDir+"/"+"xstb_ana.xlsx", filePath, total, title)
|
||
return &ReportRes{
|
||
ReportName: "销售同比分析",
|
||
Title: title,
|
||
Path: filePath,
|
||
Data: total,
|
||
}, 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 {
|
||
return nil, err
|
||
}
|
||
var product2IdMap = make(map[string]int32)
|
||
for _, v := range data.List {
|
||
product2IdMap[v.OfficialProductName] = v.OfficialProductId
|
||
}
|
||
var ids []int32
|
||
for _, v := range productNames {
|
||
if id, ok := product2IdMap[v]; ok {
|
||
ids = append(ids, id)
|
||
}
|
||
}
|
||
return ids, nil
|
||
}
|
||
|
||
func adjustedTime(t time.Time) string {
|
||
adjusted := time.Date(
|
||
t.Year(), t.Month(), t.Day(),
|
||
t.Hour(), t.Minute(), 59, 999_000_000,
|
||
t.Location(),
|
||
)
|
||
return adjusted.Format("2006-01-02 15:04:05.999")
|
||
}
|