package bbxt import ( "ai_scheduler/internal/pkg" "fmt" "reflect" "sort" "time" "github.com/xuri/excelize/v2" ) type BbxtTools struct { cacheDir string excelTempDir string } 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(today time.Time) (err error) { err = b.StatisOursProductLossSumTotal([]string{ time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()).Format("2006-01-02 15:04:05"), time.Date(today.Year(), today.Month(), today.Day(), 23, 59, 59, 0, today.Location()).Format("2006-01-02 15:04:05"), }) if err != nil { return } return } // OursProductLossSum 负利润分析 func (b *BbxtTools) StatisOursProductLossSumTotal(ct []string) (err error) { 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 } } for _, v := range resellerMap { if v.Total <= -100 { total = append(total, []string{ fmt.Sprintf("%s", v.ResellerName), fmt.Sprintf("%.2f", v.Total), }) } if v.Total <= -500 { gt = append(gt, v) } } //总量生成excel if len(total) > 0 { filePath := b.cacheDir + "/kshj_total" + fmt.Sprintf("%d", time.Now().Unix()) + ".xlsx" err = b.SimpleFillExcel(b.excelTempDir+"/"+"kshj_total_v2.xlsx", filePath, total) } if len(gt) > 0 { filePath := b.cacheDir + "/kshj_gt" + fmt.Sprintf("%d", time.Now().Unix()) + ".xlsx" // err = b.SimpleFillExcel(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, total) err = b.resellerDetailFillExcel(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt) } return err } // 最简单的通用函数 func (b *BbxtTools) SimpleFillExcel(templatePath, outputPath string, dataSlice interface{}) error { // 1. 打开模板 f, err := excelize.OpenFile(templatePath) if err != nil { return err } defer f.Close() sheet := f.GetSheetName(0) // 2. 反射获取切片数据 v := reflect.ValueOf(dataSlice) if v.Kind() != reflect.Slice { return fmt.Errorf("dataSlice must be a slice") } // 3. 从第2行开始填充 row := 2 for i := 0; i < v.Len(); i++ { item := v.Index(i).Interface() currentRow := row + i // 4. 将item转换为一行数据 var rowData []interface{} // 如果是切片 if reflect.TypeOf(item).Kind() == reflect.Slice { itemV := reflect.ValueOf(item) for j := 0; j < itemV.Len(); j++ { rowData = append(rowData, itemV.Index(j).Interface()) } } else if reflect.TypeOf(item).Kind() == reflect.Struct { itemV := reflect.ValueOf(item) for j := 0; j < itemV.NumField(); j++ { if itemV.Field(j).CanInterface() { rowData = append(rowData, itemV.Field(j).Interface()) } } } else { rowData = []interface{}{item} } // 5. 填充到Excel for col, value := range rowData { cell := fmt.Sprintf("%c%d", 'A'+col, currentRow) f.SetCellValue(sheet, cell, value) } } // 6. 保存 return f.SaveAs(outputPath) } // 分销商负利润详情填充excel // 1.使用模板文件作为输出文件 // 2.分销商总计使用第二行样式(宽高、背景、颜色等) // 3.商品详情使用第三行样式(宽高、背景、颜色等) // 4.保存为新文件 func (b *BbxtTools) resellerDetailFillExcel(templatePath, outputPath string, dataSlice []*ResellerLoss) error { // 1. 读取模板 f, err := excelize.OpenFile(templatePath) if err != nil { return err } defer f.Close() sheet := f.GetSheetName(0) // 获取模板样式1:第二行-分销商总计 resellerTplRow := 2 styleIDReseller, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", resellerTplRow)) if err != nil { // 如果获取失败,就不应用样式,或者记录日志,这里选择忽略错误继续 styleIDReseller = 0 } rowHeightReseller, err := f.GetRowHeight(sheet, resellerTplRow) if err != nil { rowHeightReseller = 31 // 默认高度 } // 获取模板样式2:第三行-产品亏损明细 productTplRow := 3 styleIDProduct, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", productTplRow)) if err != nil { // 如果获取失败,就不应用样式,或者记录日志,这里选择忽略错误继续 styleIDProduct = 0 } rowHeightProduct, err := f.GetRowHeight(sheet, productTplRow) if err != nil { rowHeightProduct = 25 // 默认高度 } currentRow := 2 for _, reseller := range dataSlice { // 3. 填充经销商数据 (ResellerName, Total) // 设置行高 f.SetRowHeight(sheet, currentRow, rowHeightReseller) // 设置单元格值 f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), reseller.ResellerName) f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), reseller.Total) // 应用样式 if styleIDReseller != 0 { f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("B%d", currentRow), styleIDReseller) } currentRow++ // 4. 填充产品亏损明细 // 先对 ProductLoss 进行排序 var products []ProductLoss for _, p := range reseller.ProductLoss { products = append(products, p) } // 按 Loss 升序排序 (亏损越多越靠前,负数越小) sort.Slice(products, func(i, j int) bool { return products[i].Loss < products[j].Loss }) for _, p := range products { // 设置行高 f.SetRowHeight(sheet, currentRow, rowHeightProduct) // 设置单元格值 f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), p.ProductName) f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), p.Loss) // 应用样式 if styleIDProduct != 0 { f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("B%d", currentRow), styleIDProduct) } currentRow++ } } // 6. 保存 return f.SaveAs(outputPath) }