ai_scheduler/internal/tools/bbxt/bbxt.go

259 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}