package models import ( "bytes" "encoding/json" "errors" "fmt" "github.com/codinl/go-logger" "github.com/xuri/excelize/v2" "go-admin/app/admin/models/tools" orm "go-admin/common/global" "gorm.io/gorm" "reflect" "strconv" "strings" ) type CategoryExcel struct { FirstCategory string `json:"first_category" binding:"required"` // 一级分类 SecondCategory string `json:"second_category"` // 二级分类 ThreeCategory string `json:"three_category"` // 三级分类 } type CommodityExcel struct { Category string `json:"category" binding:"required"` // 商品所属分类 Name string `json:"name" binding:"required"` // 商品名称 IMEIType string `json:"imei_type" binding:"required"` // 是否串码 1-无串码 2-串码(系统生成) 3-串码(手动添加) SysGenerate string `json:"sys_generate" binding:"required"` // 系统生成串码 SupplierName string `json:"supplier_name" binding:"required"` // 供应商名称 SellBrokerage string `json:"sell_brokerage" binding:"required"` // 销售毛利提成 StaffBrokerage string `json:"staff_brokerage" binding:"required"` // 员工毛利提成 MemberDiscount string `json:"member_discount" binding:"required"` // 会员优惠 RetailPrice string `json:"retail_price" binding:"required"` // 指导零售价 MinRetailPrice string `json:"min_retail_price" binding:"required"` // 最低零售价 WholesalePrice string `json:"wholesale_price" binding:"required"` // 指导采购价 StaffCostPrice string `json:"staff_cost_price" binding:"required"` // 员工成本价加价 Origin string `json:"origin"` // 产地 Remark string `json:"remark"` // 备注 } // 获取struct的tag标签,匹配excel导入数据 func getJSONTagNames(s interface{}) []string { valueType := reflect.TypeOf(s) if valueType.Kind() != reflect.Struct { fmt.Println("传入的不是结构体") return nil } var tagNames []string for i := 0; i < valueType.NumField(); i++ { field := valueType.Field(i) tag := field.Tag.Get("json") if tag != "" { tagNames = append(tagNames, tag) } } return tagNames } // 预览excel数据(不做必填项校验) func FileExcelReader(d []byte, cols []string) ([]byte, []map[string]interface{}, error) { reader, err := excelize.OpenReader(bytes.NewReader(d)) if err != nil { return nil, nil, fmt.Errorf("open reader error: %v", err) } sheetList := reader.GetSheetList() if len(sheetList) == 0 { return nil, nil, errors.New("sheet list is empty") } rows, err := reader.Rows(sheetList[0]) if err != nil { return nil, nil, fmt.Errorf("reader rows error: %v", err) } sheetCols, err := reader.GetCols(sheetList[0]) if err != nil { return nil, nil, fmt.Errorf("reader get cols error: %v", err) } if len(sheetCols) == 0 { return nil, nil, errors.New("get cols is empty") } var colsMap []map[string]interface{} if len(cols) == 0 { switch sheetList[0] { case "导分类": cols = getJSONTagNames(CategoryExcel{}) case "导商品": cols = getJSONTagNames(CommodityExcel{}) default: cols = make([]string, len(sheetCols)) for i := range sheetCols { cols[i] = fmt.Sprintf("c%02d", i) } } } else if len(cols) != len(sheetCols) { return nil, nil, errors.New("cols length does not match the number of columns") } for rows.Next() { columns, err := rows.Columns() if err != nil { return nil, nil, fmt.Errorf("rows columns error: %v", err) } columnMap := make(map[string]interface{}, len(cols)) for i, col := range cols { if i < len(columns) { columnMap[col] = columns[i] } else { columnMap[col] = "" } } logger.Info("columnMap:", columnMap) colsMap = append(colsMap, columnMap) } logger.Info("colsMap:", colsMap) mCols, err := json.Marshal(colsMap) if err != nil { return mCols, nil, fmt.Errorf("marshal error: %v", err) } return mCols, colsMap, nil } // 导入excel数据(校验必填项) func FileExcelImport(d []byte, cols []string, ntype int) ([]byte, []map[string]interface{}, error) { reader, err := excelize.OpenReader(bytes.NewReader(d)) if err != nil { return nil, nil, fmt.Errorf("open reader error: %v", err) } sheetList := reader.GetSheetList() if len(sheetList) == 0 { return nil, nil, errors.New("sheet list is empty") } rows, err := reader.Rows(sheetList[0]) if err != nil { return nil, nil, fmt.Errorf("reader rows err: %v", err) } sheetCols, err := reader.GetCols(sheetList[0]) if err != nil { return nil, nil, fmt.Errorf("reader get cols err: %v", err) } if len(sheetCols) == 0 { return nil, nil, errors.New("get cols is empty") } var colsMap []map[string]interface{} if len(cols) == 0 { switch ntype { // 导入类型:1 商品分类 2 商品资料 case 1: if sheetList[0] != "导分类" { return nil, nil, errors.New("格式错误,不是分类模版excel") } if err := checkCategoryExcel(sheetCols); err != nil { return nil, nil, err } cols = getJSONTagNames(CategoryExcel{}) case 2: if sheetList[0] != "导商品" { return nil, nil, errors.New("格式错误,不是商品模版excel") } if err := checkCommodityExcel(sheetCols); err != nil { return nil, nil, err } cols = getJSONTagNames(CommodityExcel{}) default: return nil, nil, errors.New("格式错误,不是商品分类或资料模版excel") } } else if len(cols) != len(sheetCols) { return nil, nil, errors.New("cols length does not match the number of columns") } for rows.Next() { columns, err := rows.Columns() if err != nil { return nil, nil, fmt.Errorf("rows columns err: %v", err) } columnMap := make(map[string]interface{}, len(cols)) for i, col := range cols { if i < len(columns) { columnMap[col] = columns[i] } else { columnMap[col] = "" } } logger.Info("columnMap:", columnMap) colsMap = append(colsMap, columnMap) } logger.Info("colsMap:", colsMap) mCols, err := json.Marshal(colsMap) if err != nil { return mCols, nil, fmt.Errorf("marshal err: %v", err) } return mCols, colsMap, nil } // 校验商品分类导入规则 // 导入商品分类校验报错: (1)只有3级,没有2级或1级 (2)有2,3级,但没有1级 func checkCategoryExcel(sheetCols [][]string) error { if len(sheetCols) != 3 { return errors.New("模版错误,请检查文件") } if len(sheetCols[0]) < len(sheetCols[1]) || len(sheetCols[0]) < len(sheetCols[2]) { return errors.New("格式错误,缺少一级分类") } else if len(sheetCols[1]) < len(sheetCols[2]) { return errors.New("格式错误,缺少二级分类") } for i := 1; i < len(sheetCols[2]); i++ { if sheetCols[0][i] == "" && (sheetCols[1][i] != "" || sheetCols[2][i] != "") { return errors.New("格式错误,缺少一级分类") } else if sheetCols[2][i] != "" && sheetCols[1][i] == "" { return errors.New("格式错误,缺少二级分类") } } if duplicateName, nFlag := hasDuplicateNames(sheetCols); nFlag { return fmt.Errorf("分类名称不允许重复,请检查:[%v]", duplicateName) } return nil } // 校验名称是否相同,有则false func hasDuplicateNames(sheetCols [][]string) (string, bool) { nameMap := make(map[string]struct{}) for _, col := range sheetCols { for _, name := range col { if _, exists := nameMap[name]; exists && name != "" { // 有重复的名称 return name, true } nameMap[name] = struct{}{} } } // 没有重复的名称 return "", false } // 校验商品资料导入规则 // 导入商品资料校验报错:必填项缺少数据(除了产地、备注,其他都是必填项) func checkCommodityExcel(sheetCols [][]string) error { if len(sheetCols) != 14 { return errors.New("模版错误,请检查文件") } for i := 0; i < len(sheetCols)-2; i++ { if len(sheetCols[i]) < len(sheetCols[i+1]) { return errors.New("格式错误,有必填项未录入") } for _, v := range sheetCols[i] { if v == "" { return errors.New("格式错误,有必填项未录入") } } } return nil } // 将读取的excel数据转换成CategoryExcel struct func transCategoryData(colsMap []map[string]interface{}) []CategoryExcel { var categories []CategoryExcel // 遍历 colsMap 进行类型断言和转换 for _, col := range colsMap { var category CategoryExcel for key, value := range col { switch key { case "first_category": if name, ok := value.(string); ok { category.FirstCategory = name } case "second_category": if name, ok := value.(string); ok { category.SecondCategory = name } case "three_category": if name, ok := value.(string); ok { category.ThreeCategory = name } } } // 将处理后的数据追加到 categories 中 categories = append(categories, category) } return categories } // 导入商品分类到数据库 // 规则:分类只有3级,所有分类不允许名称完全相同 func ImportCategoryData(colsMap []map[string]interface{}, businessId uint32) error { data := transCategoryData(colsMap) if duplicateName, nFlag := hasDuplicateInDb(data); nFlag { return fmt.Errorf("数据库已有此分类名称,不允许重复,请检查:[%v]", duplicateName) } for _, item := range data { var firstCategory, secondCategory, threeCategory Category if item.FirstCategory == "" { // 一级分类名称为空则跳过,继续遍历 continue } // 插入或获取一级分类 err := orm.Eloquent.Debug().FirstOrCreate(&firstCategory, Category{ Name: item.FirstCategory, Pid: 0, Display: 1, CooperativeBusinessId: businessId, }).Error if err != nil { return err } // 插入或获取二级分类 if item.SecondCategory != "" { err = orm.Eloquent.Debug().FirstOrCreate(&secondCategory, Category{ Name: item.SecondCategory, Pid: firstCategory.ID, Display: 1, CooperativeBusinessId: businessId, }).Error if err != nil { return err } } // 插入三级分类 if item.ThreeCategory != "" { err = orm.Eloquent.Debug().FirstOrCreate(&threeCategory, Category{ Name: item.ThreeCategory, Pid: secondCategory.ID, Display: 1, CooperativeBusinessId: businessId, }).Error if err != nil { return err } } } return nil } // 判断是否存在重复数据 func hasDuplicateInDb(data []CategoryExcel) (string, bool) { for _, item := range data { if exists := isCategoryExists(item.FirstCategory); exists { return item.FirstCategory, true } if exists := isCategoryExists(item.SecondCategory); exists { return item.SecondCategory, true } if exists := isCategoryExists(item.ThreeCategory); exists { return item.ThreeCategory, true } } return "", false } // 判断分类是否存在 func isCategoryExists(categoryName string) bool { var count int64 orm.Eloquent.Debug().Model(&Category{}). Where("name = ?", categoryName). Count(&count) return count > 0 } // 导入商品资料 func ImportCommodityData(colsMap []map[string]interface{}, businessId uint32) error { data, err := transCommodityData(colsMap) if err != nil { return err } var erpCommodities []ErpCommodity erpCommodities, err = convertToErpCommodities(data) if err != nil { return err } err = updateOrInsertCommodities(erpCommodities) if err != nil { return err } return nil } // 将读取的excel数据转换成CommodityExcel struct func transCommodityData(colsMap []map[string]interface{}) ([]CommodityExcel, error) { var commodities []CommodityExcel // 遍历 colsMap 进行类型断言和转换 for _, col := range colsMap { var commodity CommodityExcel // 将 col 转换为 JSON 字符串 jsonData, err := json.Marshal(col) if err != nil { return nil, fmt.Errorf("failed to marshal data to JSON: %v", err) } // 将 JSON 字符串反序列化为 CommodityExcel if err := json.Unmarshal(jsonData, &commodity); err != nil { return nil, fmt.Errorf("failed to unmarshal JSON to CommodityExcel: %v", err) } // 将处理后的数据追加到 commodities 中 commodities = append(commodities, commodity) } return commodities, nil } func convertToErpCommodities(data []CommodityExcel) ([]ErpCommodity, error) { var erpCommodities []ErpCommodity productCounter := tools.NewProductCounter() for _, item := range data { category, err := getCategoryByName(item.Category) if err != nil { return nil, err } list, err := GetSupplier(GetSupplierRequest{Name: item.SupplierName}) if len(list) == 0 || errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("有供应商未导入,请检查:[%v]", item.SupplierName) } serialNumber := fmt.Sprintf("%s%04d", category.Number, getNumberOnCategory(category.ID)+int64(productCounter.GetNextProductNumber(category.Number))) fmt.Println("商品编号:", serialNumber) erpCommodity, err := convertToErpCommodity(item, category, list[0], serialNumber) if err != nil { return nil, err } erpCommodities = append(erpCommodities, erpCommodity) } return erpCommodities, nil } // 查询商品分类 func getCategoryByName(name string) (*Category, error) { var category Category err := orm.Eloquent.Debug().Model(&Category{}). Where("name = ?", name).First(&category).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("有商品分类未导入,请检查:[%v]", name) } return &category, err } // 转换格式,匹配Commodity的model func convertToErpCommodity(item CommodityExcel, category *Category, supplier *Supplier, serialNumber string) (ErpCommodity, error) { brokerage1Float, err := strconv.ParseFloat(strings.TrimRight(item.SellBrokerage, "%"), 64) // 销售毛利 if err != nil { return ErpCommodity{}, fmt.Errorf("销售毛利转换有误:[%v]", err) } brokerage2Float, err := strconv.ParseFloat(strings.TrimRight(item.StaffBrokerage, "%"), 64) // 员工毛利提成 if err != nil { return ErpCommodity{}, fmt.Errorf("员工毛利提成转换有误:[%v]", err) } memberDiscountFloat, err := strconv.ParseFloat(item.MemberDiscount, 64) // 会员优惠 if err != nil { return ErpCommodity{}, fmt.Errorf("会员优惠转换有误:[%v]", err) } nRetailPrice, err := strconv.ParseUint(item.RetailPrice, 10, 32) // 指导零售价 if err != nil { return ErpCommodity{}, fmt.Errorf("指导零售价转换有误:[%v]", err) } nMinRetailPrice, err := strconv.ParseUint(item.MinRetailPrice, 10, 32) // 最低零售价 if err != nil { return ErpCommodity{}, fmt.Errorf("最低零售价转换有误:[%v]", err) } nStaffCostPrice, err := strconv.ParseUint(item.StaffCostPrice, 10, 32) // 员工成本价加价 if err != nil { return ErpCommodity{}, fmt.Errorf("员工成本价加价转换有误:[%v]", err) } nWholesalePrice, err := strconv.ParseUint(item.WholesalePrice, 10, 32) // 指导采购价 if err != nil { return ErpCommodity{}, fmt.Errorf("指导采购价转换有误:[%v]", err) } //将是否串码转换为数字 1-无串码 2-串码(系统生成) 3-串码(手动添加) var nIMEIType uint32 switch item.IMEIType { case "串码类": if item.SysGenerate == "是" { nIMEIType = 2 } else if item.SysGenerate == "否" { nIMEIType = 3 } else { return ErpCommodity{}, errors.New("[系统生成串码]列有非法字段") } case "非串码": nIMEIType = 1 default: return ErpCommodity{}, errors.New("[是否串码]列有非法字段") } //serialNumber := fmt.Sprintf("%s%04d", category.Number, getNumberOnCategory(category.ID)+1) //fmt.Println("商品编号:", serialNumber) return ErpCommodity{ SerialNumber: serialNumber, Name: item.Name, ErpCategoryId: category.ID, ErpCategoryName: item.Category, IMEIType: nIMEIType, ErpSupplierId: supplier.ID, ErpSupplierName: item.SupplierName, RetailPrice: uint32(nRetailPrice), MinRetailPrice: uint32(nMinRetailPrice), StaffCostPrice: uint32(nStaffCostPrice), WholesalePrice: uint32(nWholesalePrice), Brokerage1: brokerage1Float, Brokerage2: brokerage2Float, MemberDiscount: memberDiscountFloat, Origin: item.Origin, Remark: item.Remark, }, nil } // 操作数据库,通过商品名称查找,没有则插入,有则更新 func updateOrInsertCommodities(erpCommodities []ErpCommodity) error { for _, erpCommodity := range erpCommodities { var existingCommodity ErpCommodity result := orm.Eloquent.Where("name = ?", erpCommodity.Name).First(&existingCommodity) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { orm.Eloquent.Create(&erpCommodity) fmt.Println("Inserted:", erpCommodity.Name) } else { fmt.Println("Error:", result.Error) return result.Error } } else { orm.Eloquent.Model(&existingCommodity).Updates(&erpCommodity) fmt.Println("Updated:", erpCommodity.Name) } } return nil } // 查询商品列表中同一类商品分类下商品的个数 func getNumberOnCategory(categoryId uint32) int64 { var count int64 orm.Eloquent.Debug().Model(&ErpCommodity{}). Where("erp_category_id = ?", categoryId). Count(&count) return count } // 生成商品编号 func GenerateSerialNumber(categoryId uint32) (string, error) { var category Category err := orm.Eloquent.Debug().Model(&Category{}). Where("id = ?", categoryId).First(&category).Error if errors.Is(err, gorm.ErrRecordNotFound) { return "", errors.New("未查到商品分类") } var count int64 orm.Eloquent.Debug().Model(&ErpCommodity{}). Where("erp_category_id = ?", category.ID). Count(&count) serialNumber := fmt.Sprintf("%s%04d", category.Number, count+1) fmt.Println("商品编号:", serialNumber) return serialNumber, nil }