package bbxt import ( "ai_scheduler/internal/config" pkginner "ai_scheduler/internal/pkg" "ai_scheduler/internal/pkg/lsxd" "ai_scheduler/internal/pkg/utils_oss" "ai_scheduler/pkg" "context" "fmt" "math/rand" "slices" "sort" "time" ) const ( RedStyle = "${color: FF0000;horizontal:center;vertical:center;borderColor:#000000}" GreenStyle = "${color: 008000;horizontal:center;vertical:center;borderColor:#000000}" ) const ( IndexLossSumDetail = "lossSumDetail" ) type LossSumInitFunc func(ctx context.Context, day time.Time, totalDetail []*ResellerLoss, selfObj *BbxtTools) error var ( DownWardValue int32 = 1000 SumFilter int32 = -150 ) var resellerBlackListProduct = []string{ "悦跑", "电商-独立", "蓝星严选连续包月", "通钱-2025年12月", "彦浩同行", } type BbxtTools struct { cacheDir string excelTempDir string ossClient *utils_oss.Client config *config.Config login *lsxd.Login } func NewBbxtTools(config *config.Config, login *lsxd.Login) (*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), config: config, login: login, }, nil } func (b *BbxtTools) DailyReport( ctx context.Context, now time.Time, downWardValue int32, productName []string, sumFilter int32, ossClient *utils_oss.Client, initFunc LossSumInitFunc, ) (reports []*ReportRes, err error) { reports = make([]*ReportRes, 0, 4) productLossReport, err := b.StatisOursProductLossSum(ctx, now, initFunc) 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, profitRankingSum, statisOfficialProductSum, statisOfficialProductSumDecline) if ossClient != nil { uploader := NewUploader(ossClient, b.config) for _, report := range reports { _ = uploader.Run(report) } } return } // StatisOursProductLossSum 负利润分析 func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, initFunc LossSumInitFunc) (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 totalDetail []*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(resellerBlackListProduct, v.ResellerName) { total = append(total, []string{ fmt.Sprintf("%s", v.ResellerName), fmt.Sprintf("%.2f", v.Total), }) totalSum += v.Total totalDetail = append(totalDetail, v) } if v.Total <= -500 && !slices.Contains(resellerBlackListProduct, v.ResellerName) { gt = append(gt, v) totalSum500 += v.Total } } report = make([]*ReportRes, 3) 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, "") // if err != nil { // return // } // report[0] = &ReportRes{ // ReportName: "分销商负利润统计", // Title: "截至" + timeCh + "利润累计亏损" + fmt.Sprintf("%.2f", totalSum), // Path: filePath, // Data: total, // } //} //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) // if err != nil { // return // } // report[1] = &ReportRes{ // ReportName: "负利润分析(亏损500以上)", // Title: "截至" + timeCh + "亏损500以上利润累计亏损" + fmt.Sprintf("%.2f", totalSum500), // Path: filePath, // Data: total, // } //} if len(totalDetail) > 0 { err = initFunc(ctx, now, totalDetail, b) if err != nil { return } filePath := b.cacheDir + "/kshj_total_ana" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx" title := "截至" + timeCh + "亏损100以上的分销商&产品负利润原因" err = b.resellerDetailFillExcelAna(b.excelTempDir+"/"+"kshj_total_ana.xlsx", filePath, totalDetail, title) if err != nil { return } report[2] = &ReportRes{ ReportName: "负利润分析(亏损100以上)", Title: "截至" + timeCh + "亏损100以上利润原因", Path: filePath, Data: total, } } return report, nil } func (b *BbxtTools) GetResellerLossMannagerAndLossReasonFromApi(ctx context.Context, resellerLoss []*ResellerLoss) (map[int32]*ResellerLossSumProductRelation, error) { var ( resellerMap = make(map[int32]map[string][]int32) resellerLossMap = make(map[int32]*ResellerLoss, len(resellerLoss)) relationMap = make(map[int32]*ResellerLossSumProductRelation) ) for _, v := range resellerLoss { var productSlice = make([]int32, 0, len(v.ProductLoss)) for _, vv := range v.ProductLoss { productSlice = append(productSlice, vv.ProductId) } if _, ex := resellerMap[v.ResellerId]; !ex { resellerMap[v.ResellerId] = make(map[string][]int32, 1) } resellerMap[v.ResellerId]["values"] = productSlice resellerLossMap[v.ResellerId] = v relationMap[v.ResellerId] = &ResellerLossSumProductRelation{ ResellerName: v.ResellerName, Products: make(map[int32]*LossReason, len(v.ProductLoss)), } for _, product := range v.ProductLoss { relationMap[v.ResellerId].Products[product.ProductId] = &LossReason{ ProductName: product.ProductName, LossReason: "未填写", // 初始化为未填写 } } } res, err := GetManagerAndDefaultLossReasonApi(&GetManagerAndDefaultLossReasonRequest{ Param: resellerMap, }, b.login.GetToken(ctx), b.config.ZLTX.ReqUrl) if err != nil { return nil, err } for _, v := range res { if v == nil { continue } if _, ok := resellerLossMap[v.ResellerInfo.ResellerId]; !ok { continue } relationMap[v.ResellerInfo.ResellerId].AfterSaleName = v.ResellerInfo.AfterSaleName for _, vv := range v.ProductList { relationMap[v.ResellerInfo.ResellerId].Products[vv.ProductId].LossReason = vv.LossReason } } return relationMap, 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[string]ProductSumDecline) ) for _, v := range data.OfficialProductSumDecline { if _, ex := productSumMap[v.OfficialProductName]; !ex { productSumMap[v.OfficialProductName] = ProductSumDecline{ OfficialProductName: v.OfficialProductName, OfficialProductId: v.OfficialProductId, ProductSumReseller: make(map[int32]ProductSumReseller), Index: productMap[v.OfficialProductName], } } if v.HistoryOneDiff <= sumFilter || v.HistoryTwoDiff <= sumFilter { productSumMap[v.OfficialProductName].ProductSumReseller[v.ResellerId] = ProductSumReseller{ ResellerName: v.ResellerName, CurrentNum: v.CurrentNum, HistoryOneNum: v.HistoryOneNum, HistoryOneDiff: v.HistoryOneDiff, HistoryTwoNum: v.HistoryTwoNum, HistoryTwoDiff: v.HistoryTwoDiff, } } } var total = make([]ProductSumDecline, 0, len(productSumMap)) for k, _ := range productSumMap { total = append(total, productSumMap[k]) } sort.Slice(total, func(i, j int) bool { return total[i].Index > total[j].Index }) 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, total, 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") }