diff --git a/internal/biz/do/macro.go b/internal/biz/do/macro.go index 9d68eda..fe1ffc5 100644 --- a/internal/biz/do/macro.go +++ b/internal/biz/do/macro.go @@ -1,11 +1,15 @@ package do import ( + "ai_scheduler/internal/config" "ai_scheduler/internal/data/impl" "ai_scheduler/internal/data/model" "ai_scheduler/internal/pkg" - + "ai_scheduler/internal/pkg/lsxd" "ai_scheduler/internal/tools/bbxt" + "ai_scheduler/utils" + "database/sql" + "errors" "context" "encoding/json" @@ -23,19 +27,25 @@ import ( type Macro struct { botGroupImpl *impl.BotGroupImpl reportDailyCacheImpl *impl.ReportDailyCacheImpl + config *config.Config + rdb *utils.Rdb } func NewMacro( botGroupImpl *impl.BotGroupImpl, reportDailyCacheImpl *impl.ReportDailyCacheImpl, + config *config.Config, + rdb *utils.Rdb, ) *Macro { return &Macro{ botGroupImpl: botGroupImpl, reportDailyCacheImpl: reportDailyCacheImpl, + config: config, + rdb: rdb, } } -type MacroFunc func(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) +type MacroFunc func(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig, config *config.Config) (successMsg string, err error, isFinish bool) func (m *Macro) Router(ctx context.Context, reqContent string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) { reqContent = strings.TrimSpace(reqContent) @@ -47,16 +57,16 @@ func (m *Macro) Router(ctx context.Context, reqContent string, groupConfig *mode case strings.HasPrefix(reqContent, "[负利润分析]导出"): return m.NegativeProfitGet(ctx) case strings.HasPrefix(reqContent, "[负利润分析]更新"): - return m.NegativeProfitUpdate(ctx, reqContent, groupConfig) + return m.NegativeProfitUpdate(ctx, reqContent) case strings.HasPrefix(reqContent, "[负利润分析]清理"): - return m.NegativeProfitClear(ctx, reqContent, groupConfig) + return m.NegativeProfitClear() default: } return } -func (m *Macro) NegativeProfitClear(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) { +func (m *Macro) NegativeProfitClear() (successMsg string, err error, isFinish bool) { dayDate := time.Now().Format(time.DateOnly) cond := builder.NewCond() cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}) @@ -74,53 +84,88 @@ func (m *Macro) NegativeProfitClear(ctx context.Context, content string, groupCo return } -func (m *Macro) NegativeProfitUpdate(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) { +func (m *Macro) NegativeProfitUpdate(ctx context.Context, content string) (successMsg string, err error, isFinish bool) { //newContent := strings.ReplaceAll(strings.TrimSpace(content), "[负利润分析]更新:", "") jsonData, err := ParseLossData(content) + b, err := bbxt.NewBbxtTools(m.config, lsxd.NewLogin(m.config, m.rdb)) if err != nil { - err = fmt.Errorf("解析失败:%v", err) return } - dayDate := time.Now().Format(time.DateOnly) + now := time.Now() + dayData := now.Format(time.DateOnly) + value, err := b.GetMapResellerLossSumProductRelation(ctx, now, m.GetReportCache) + if err != nil { + return + } + var setData = make(map[int32]*bbxt.ResellerLossSumProductRelation) + for k, v := range jsonData { + if _, ok := value[k]; !ok { + continue + } + for productId, product := range v.Products { + if _, ok := value[k].Products[productId]; !ok { + continue + } + if value[k].Products[productId].LossReason == product.LossReason { + continue + } + if _, ex := setData[k]; !ex { + setData[k] = &bbxt.ResellerLossSumProductRelation{ + ResellerName: value[k].ResellerName, + AfterSaleName: value[k].AfterSaleName, + Products: make(map[int32]*bbxt.LossReason), + } + } + setData[k].Products[productId] = &bbxt.LossReason{ + ProductName: product.ProductName, + LossReason: product.LossReason, + } + } + } + cond := builder.NewCond() cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}) - cond = cond.And(builder.Eq{"cache_key": dayDate}) - err = m.reportDailyCacheImpl.UpdateByCond(&cond, &model.AiReportDailyCache{ - Value: pkg.JsonStringIgonErr(jsonData), - }) + cond = cond.And(builder.Eq{"cache_key": dayData}) + cond = cond.And(builder.Eq{"status": 1}) + var cache model.AiReportDailyCache + err = m.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache) if err != nil { - err = fmt.Errorf("解析失败:%v", err) return } + if cache.ID == 0 { + + cache = model.AiReportDailyCache{ + CacheKey: dayData, + CacheIndex: bbxt.IndexLossSumDetail, + Value: pkg.JsonStringIgonErr(setData), + } + _, err = m.reportDailyCacheImpl.Add(&cache) + if err != nil { + return + } + } else { + err = m.reportDailyCacheImpl.UpdateByCond(&cond, &model.AiReportDailyCache{ + Value: pkg.JsonStringIgonErr(setData), + }) + if err != nil { + return + } + } isFinish = true successMsg = "更新成功" return } func (m *Macro) NegativeProfitGet(ctx context.Context) (successMsg string, err error, isFinish bool) { - var ( - data model.AiReportDailyCache - value map[int32]*bbxt.ResellerLossSumProductRelation - ) - isFinish = true - cond := builder.NewCond() - cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}) - cond = cond.And(builder.Eq{"cache_key": time.Now().Format(time.DateOnly)}) - err = m.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &data) + b, err := bbxt.NewBbxtTools(m.config, lsxd.NewLogin(m.config, m.rdb)) if err != nil { - err = fmt.Errorf("获取失败:%v", err) return } - if data.ID == 0 { - successMsg = "暂未获取今日负利润分析数据,请先呼出报表数据" - return - } - err = json.Unmarshal([]byte(data.Value), &value) + now := time.Now() + value, err := b.GetMapResellerLossSumProductRelation(ctx, now, m.GetReportCache) if err != nil { - err = fmt.Errorf("获取失败,格式解析错误:%v", err) return } - //将value转为string //**[供应商id]供应商名称->商务名称**\n //└──商品名称:亏损原因\n @@ -136,6 +181,7 @@ func (m *Macro) NegativeProfitGet(ctx context.Context) (successMsg string, err e } } successMsg = valueString.String() + isFinish = true return } @@ -306,3 +352,109 @@ func ParseLossData(input string) (map[int32]*bbxt.ResellerLossSumProductRelation return result, nil } + +func (m *Macro) GetReportCache(ctx context.Context, day time.Time, totalDetail []*bbxt.ResellerLoss, bbxtObj *bbxt.BbxtTools) error { + dayDate := day.Format(time.DateOnly) + + // 1. 从 API 获取数据并填充 + apiRelations, err := bbxtObj.GetResellerLossMannagerAndLossReasonFromApi(ctx, totalDetail) + if err != nil { + return fmt.Errorf("get API data failed: %w", err) + } + + // 使用 API 数据填充损失原因 + fillLossReasonFromData(totalDetail, apiRelations, "未填写") + + // 2. 从缓存获取数据并覆盖 + cachedRelations, err := m.getCachedRelations(dayDate) + if err != nil { + return fmt.Errorf("get cache data failed: %w", err) + } + + // 使用缓存数据覆盖损失原因 + if cachedRelations != nil { + fillLossReasonFromData(totalDetail, cachedRelations, "") + } + + return nil +} + +// 从缓存获取关系数据 +func (m *Macro) getCachedRelations(dayDate string) (map[int32]*bbxt.ResellerLossSumProductRelation, error) { + cond := builder.NewCond(). + And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}). + And(builder.Eq{"cache_key": dayDate}). + And(builder.Eq{"status": 1}) + + var cache model.AiReportDailyCache + err := m.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + // 缓存不存在是正常情况 + return nil, nil + } + return nil, fmt.Errorf("query cache failed: %w", err) + } + + if cache.ID == 0 { + return nil, nil + } + + var relations map[int32]*bbxt.ResellerLossSumProductRelation + if err := json.Unmarshal([]byte(cache.Value), &relations); err != nil { + return nil, fmt.Errorf("unmarshal cache failed: %w", err) + } + + return relations, nil +} + +// 使用指定数据源填充损失原因 +func fillLossReasonFromData( + totalDetail []*bbxt.ResellerLoss, + relations map[int32]*bbxt.ResellerLossSumProductRelation, + defaultReason string, // 当数据不存在时使用的默认值 +) { + for _, detail := range totalDetail { + resellerRelation, exists := relations[detail.ResellerId] + if !exists { + continue + } + + // 设置售后经理(只有在有值时才设置) + if resellerRelation.AfterSaleName != "" { + detail.Manager = resellerRelation.AfterSaleName + } + + // 为每个产品设置损失原因 + for _, product := range detail.ProductLoss { + setProductLossReason(product, resellerRelation, defaultReason) + } + } +} + +// 设置单个产品的损失原因 +func setProductLossReason( + product *bbxt.ProductLoss, + resellerRelation *bbxt.ResellerLossSumProductRelation, + defaultReason string, +) { + // 如果该经销商没有产品数据,跳过 + if resellerRelation.Products == nil { + return + } + + productRelation, exists := resellerRelation.Products[product.ProductId] + if !exists { + if defaultReason != "" { + product.LossReason = defaultReason + } + return + } + + // 如果有损失原因则设置,否则使用默认值 + if productRelation.LossReason != "" { + product.LossReason = productRelation.LossReason + } else if defaultReason != "" { + product.LossReason = defaultReason + } +} diff --git a/internal/biz/group_config.go b/internal/biz/group_config.go index 8987119..9a8bb82 100644 --- a/internal/biz/group_config.go +++ b/internal/biz/group_config.go @@ -1,6 +1,7 @@ package biz import ( + "ai_scheduler/internal/biz/do" "ai_scheduler/internal/biz/tools_regis" "ai_scheduler/internal/config" "ai_scheduler/internal/data/constants" @@ -9,7 +10,6 @@ import ( "ai_scheduler/internal/domain/workflow/recharge" "ai_scheduler/internal/domain/workflow/runtime" "ai_scheduler/internal/entitys" - "ai_scheduler/internal/pkg" "ai_scheduler/internal/pkg/l_request" "ai_scheduler/internal/pkg/lsxd" "ai_scheduler/internal/pkg/utils_oss" @@ -41,6 +41,7 @@ type GroupConfigBiz struct { toolManager *tools.Manager conf *config.Config rdb *utils.Rdb + macro *do.Macro } // NewDingTalkBotBiz @@ -52,6 +53,7 @@ func NewGroupConfigBiz( conf *config.Config, reportDailyCacheImpl *impl.ReportDailyCacheImpl, rdb *utils.Rdb, + macro *do.Macro, ) *GroupConfigBiz { return &GroupConfigBiz{ botTools: tools.BootTools, @@ -61,6 +63,7 @@ func NewGroupConfigBiz( conf: conf, reportDailyCacheImpl: reportDailyCacheImpl, rdb: rdb, + macro: macro, } } @@ -86,7 +89,7 @@ func (g *GroupConfigBiz) GetReportLists(ctx context.Context, groupConfig *model. return } - reports, err = reportList.DailyReport(ctx, time.Now(), bbxt.DownWardValue, product, bbxt.SumFilter, g.ossClient, g.GetReportCache) + reports, err = reportList.DailyReport(ctx, time.Now(), bbxt.DownWardValue, product, bbxt.SumFilter, g.ossClient, g.macro.GetReportCache) if err != nil { return } @@ -163,7 +166,7 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz var reports []*bbxt.ReportRes switch rec.Match.Index { case "report_loss_analysis": - repo, _err := rep.StatisOursProductLossSum(ctx, t, g.GetReportCache) + repo, _err := rep.StatisOursProductLossSum(ctx, t, g.macro.GetReportCache) if _err != nil { return _err } @@ -184,7 +187,7 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz reports = append(reports, repo) case "report_daily": product := strings.Split(groupConfig.ProductName, ",") - repo, _err := rep.DailyReport(ctx, t, bbxt.DownWardValue, product, bbxt.SumFilter, nil, g.GetReportCache) + repo, _err := rep.DailyReport(ctx, t, bbxt.DownWardValue, product, bbxt.SumFilter, nil, g.macro.GetReportCache) if _err != nil { return _err } @@ -413,55 +416,3 @@ func (g *GroupConfigBiz) otherTask(ctx context.Context, rec *entitys.Recognize) entitys.ResText(rec.Ch, "", rec.Match.Reasoning) return } - -func (g *GroupConfigBiz) GetReportCache(ctx context.Context, day time.Time, totalDetail []*bbxt.ResellerLoss, bbxtObj *bbxt.BbxtTools) error { - var ResellerProductRelation map[int32]*bbxt.ResellerLossSumProductRelation - - dayDate := day.Format(time.DateOnly) - cond := builder.NewCond() - cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}) - cond = cond.And(builder.Eq{"cache_key": dayDate}) - cond = cond.And(builder.Eq{"status": 1}) - var cache model.AiReportDailyCache - err := g.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache) - if err != nil { - return err - } - if cache.ID == 0 { - ResellerProductRelation, err = bbxtObj.GetResellerLossMannagerAndLossReasonFromApi(ctx, totalDetail) - if err != nil { - return err - } - cache = model.AiReportDailyCache{ - CacheKey: dayDate, - CacheIndex: bbxt.IndexLossSumDetail, - Value: pkg.JsonStringIgonErr(ResellerProductRelation), - } - _, err = g.reportDailyCacheImpl.Add(&cache) - } else { - err = json.Unmarshal([]byte(cache.Value), &ResellerProductRelation) - } - - if err != nil { - return err - } - for _, v := range totalDetail { - if _, ex := ResellerProductRelation[v.ResellerId]; !ex { - continue - } - v.Manager = ResellerProductRelation[v.ResellerId].AfterSaleName - for _, vv := range v.ProductLoss { - if _, ex := ResellerProductRelation[v.ResellerId].Products[vv.ProductId]; !ex { - vv.LossReason = "未填写" - continue - } - if len(ResellerProductRelation[v.ResellerId].Products[vv.ProductId].LossReason) == 0 { - vv.LossReason = "未填写" - continue - } - vv.LossReason = ResellerProductRelation[v.ResellerId].Products[vv.ProductId].LossReason - } - } - - return nil -} diff --git a/internal/tools/bbxt/bbxt.go b/internal/tools/bbxt/bbxt.go index 9f20182..345f982 100644 --- a/internal/tools/bbxt/bbxt.go +++ b/internal/tools/bbxt/bbxt.go @@ -31,7 +31,7 @@ var ( SumFilter int32 = -150 ) -var resellerBlackListProduct = []string{ +var ResellerBlackListProduct = []string{ "悦跑", "电商-独立", "蓝星严选连续包月", @@ -103,9 +103,7 @@ func (b *BbxtTools) DailyReport( return } - -// StatisOursProductLossSum 负利润分析 -func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, initFunc LossSumInitFunc) (report []*ReportRes, err error) { +func (b *BbxtTools) ResellerLossSort(ctx context.Context, now time.Time) ([]*ResellerLoss, 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())), @@ -115,13 +113,10 @@ func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, Ct: ct, }) if err != nil { - return + return nil, err } var ( resellerMap = make(map[int32]*ResellerLoss) - total [][]string - gt []*ResellerLoss - totalDetail []*ResellerLoss ) for _, info := range data.List { @@ -166,14 +161,66 @@ func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, sort.Slice(resellers, func(i, j int) bool { return resellers[i].Total < resellers[j].Total }) - var ( - totalSum float64 + return resellers, nil +} +func (b *BbxtTools) GetMapResellerLossSumProductRelation(ctx context.Context, now time.Time, initFunc LossSumInitFunc) (map[int32]*ResellerLossSumProductRelation, error) { + resellers, err := b.ResellerLossSort(ctx, now) + if err != nil { + return nil, err + } + var totalDetail []*ResellerLoss + for _, v := range resellers { + if v.Total <= -100 && !slices.Contains(ResellerBlackListProduct, v.ResellerName) { + + totalDetail = append(totalDetail, v) + } + + } + if len(totalDetail) > 0 { + err = initFunc(ctx, now, totalDetail, b) + if err != nil { + return nil, err + } + } + value := b.ResellerLossToMapResellerLossSumProductRelation(totalDetail) + return value, nil +} + +func (b *BbxtTools) ResellerLossToMapResellerLossSumProductRelation(totalDetail []*ResellerLoss) map[int32]*ResellerLossSumProductRelation { + relations := make(map[int32]*ResellerLossSumProductRelation) + for _, detail := range totalDetail { + if _, ok := relations[detail.ResellerId]; !ok { + relations[detail.ResellerId] = &ResellerLossSumProductRelation{ + ResellerName: detail.ResellerName, + AfterSaleName: detail.Manager, + Products: make(map[int32]*LossReason), + } + } + for _, product := range detail.ProductLoss { + relations[detail.ResellerId].Products[product.ProductId] = &LossReason{ + ProductName: product.ProductName, + LossReason: product.LossReason, + } + } + + } + return relations +} + +// StatisOursProductLossSum 负利润分析 +func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, initFunc LossSumInitFunc) (report []*ReportRes, err error) { + resellers, err := b.ResellerLossSort(ctx, now) + var ( + total [][]string + gt []*ResellerLoss + totalDetail []*ResellerLoss + totalSum float64 totalSum500 float64 ) // 构建分组 for _, v := range resellers { - if v.Total <= -100 && !slices.Contains(resellerBlackListProduct, v.ResellerName) { + if v.Total <= -100 && !slices.Contains(ResellerBlackListProduct, v.ResellerName) { total = append(total, []string{ fmt.Sprintf("%s", v.ResellerName), fmt.Sprintf("%.2f", v.Total), @@ -181,7 +228,7 @@ func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, totalSum += v.Total totalDetail = append(totalDetail, v) } - if v.Total <= -500 && !slices.Contains(resellerBlackListProduct, v.ResellerName) { + if v.Total <= -500 && !slices.Contains(ResellerBlackListProduct, v.ResellerName) { gt = append(gt, v) totalSum500 += v.Total }