package models import ( "bytes" "database/sql" "encoding/json" "errors" "fmt" "github.com/xuri/excelize/v2" "go-admin/app/admin/models/tools" orm "go-admin/common/global" "go-admin/logger" tool "go-admin/tools" "gorm.io/gorm" "reflect" "strconv" "strings" "sync" "time" "unicode" "unicode/utf8" ) 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"` // 商品名称 ErpBarcode string `json:"erp_barcode"` // 商品条码 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"` // 备注 } type StockExcel struct { Name string `json:"name"` // 商品名称 SerialNum string `json:"serial_num"` // 商品编号 StoreName string `json:"store_name" binding:"required"` // 所属门店 SysGenerate string `json:"sys_generate"` // 系统生成串码 WholesalePrice string `json:"wholesale_price" binding:"required"` // 指导采购价 StaffCostPrice string `json:"staff_cost_price" binding:"required"` // 员工成本价=员工成本价加价+指导采购价 SupplierName string `json:"supplier_name" binding:"required"` // 供应商名称 StockTime string `json:"stock_time"` // 入库时间 Count string `json:"count" binding:"required"` // 数量 } type OrderExcel struct { SerialNum string `json:"serial_num"` // 商品编号 Name string `json:"name"` // 商品名称 IMEI string `json:"imei"` // 商品串码 Count string `json:"count" binding:"required"` // 销售数量 StoreName string `json:"store_name" binding:"required"` // 所属门店 SalePrice string `json:"sale_price"` // 零售价 PresentType string `json:"present_type"` // 是否赠送 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 } // FileExcelReader 预览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{}) case "导库存": cols = getJSONTagNames(StockExcel{}) 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:", logger.Field("columnMap", columnMap)) colsMap = append(colsMap, columnMap) } logger.Info("colsMap:", logger.Field("colsMap", colsMap)) mCols, err := json.Marshal(colsMap) if err != nil { return mCols, nil, fmt.Errorf("marshal error: %v", err) } return mCols, colsMap, nil } // FileExcelImport 导入excel数据(校验必填项) func FileExcelImport(d []byte, cols []string, nType, storeId 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") } rowData, err := reader.GetRows(sheetList[0]) if err != nil { return nil, nil, fmt.Errorf("reader rows error: %v", err) } 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") } sheetCols = checkExcelFormat(rowData, sheetCols) var colsMap []map[string]interface{} if len(cols) == 0 { switch nType { // 导入类型:1 商品分类 2 商品资料 3 商品库存 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{}) case 3: if sheetList[0] != "导库存" { return nil, nil, errors.New("格式错误,不是库存模版excel") } if err := checkStockExcel(sheetCols); err != nil { return nil, nil, err } cols = getJSONTagNames(StockExcel{}) case 4: if sheetList[0] != "导零售" { return nil, nil, errors.New("格式错误,不是零售模版excel") } if err := checkOrderExcel(sheetCols, storeId); err != nil { return nil, nil, err } cols = getJSONTagNames(OrderExcel{}) 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") } nCounter := len(rowData) //记录有数据的行数,空行则跳过循环 for rows.Next() { if nCounter == 0 { break } nCounter-- 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:", logger.Field("columnMap", columnMap)) colsMap = append(colsMap, columnMap) } logger.Info("colsMap:", logger.Field("colsMap", colsMap)) mCols, err := json.Marshal(colsMap) if err != nil { return mCols, nil, fmt.Errorf("marshal err: %v", err) } return mCols, colsMap, nil } // checkExcelFormat校验excel表格格式,剔除最后1行的空格 func checkExcelFormat(rowData, sheetCols [][]string) [][]string { if findMaxLength(sheetCols) > len(rowData) { // 最后1行有空格,踢除 newSheetCols := make([][]string, len(sheetCols)) for i, col := range sheetCols { if len(col) > len(rowData) { newSheetCols[i] = col[:len(rowData)] } else { newSheetCols[i] = col } } return newSheetCols } return sheetCols } // 校验商品分类导入规则 // 导入商品分类校验报错: (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 //} //// 校验名称是否相同,有则返回true和重复的名称,备注:只能校验不同的列中不能有重复数据,如:第一列的123,第二列不能有123 //func hasDuplicateNames(sheetCols [][]string) (string, bool) { // globalMap := make(map[string]struct{}) // // for _, col := range sheetCols { // colMap := make(map[string]struct{}) // for _, name := range col { // // 忽略空名称 // if name == "" { // continue // } // // // 检查全局map中是否已存在 // if _, exists := globalMap[name]; exists { // return name, true // } // // // 将名称添加到当前列map中 // colMap[name] = struct{}{} // } // // // 将当前列map中的元素添加到全局map中 // for name := range colMap { // globalMap[name] = struct{}{} // } // } // // // 没有重复的名称 // return "", false //} // 校验名称是否相同,有则返回true和重复的名称 // 第二列数据如果重复,需要看第一列同一行的数据是否相同,如果不同则报错。 // 第三列数据不能有重复的名称 func hasDuplicateNames(sheetCols [][]string) (string, bool) { //firstColMap := make(map[string]bool) secondColMap := make(map[string]string) // Map记录第二列数据的重复情况,以及对应第一列的数据 thirdColMap := make(map[string]bool) // 确保每一列数据都有相同数量的行数 rowsCount := len(sheetCols[0]) //for i, col := range sheetCols { // if len(col) != rowsCount && i != 2 { // return "每列数据的行数不一致", true // } //} for i := 0; i < rowsCount; i++ { //// 检查第一列 //firstColName := sheetCols[0][i] //if firstColName != "" { // if _, exists := firstColMap[firstColName]; exists { // return firstColName, true // } // firstColMap[firstColName] = true //} // 检查第二列 if len(sheetCols[1]) < i+1 { continue } secondColName := sheetCols[1][i] if secondColName != "" { if firstCol, exists := secondColMap[secondColName]; exists && firstCol != sheetCols[0][i] { return secondColName, true } secondColMap[secondColName] = sheetCols[0][i] } // 检查第三列 if len(sheetCols[2]) < i+1 { continue } thirdColName := sheetCols[2][i] if thirdColName != "" { if _, exists := thirdColMap[thirdColName]; exists { return thirdColName, true } thirdColMap[thirdColName] = true } } // 没有重复的名称 return "", false } // 校验商品资料导入规则 // 导入商品资料校验报错:必填项缺少数据(除了产地、备注,其他都是必填项) func checkCommodityExcel(sheetCols [][]string) error { if len(sheetCols) != 15 { return errors.New("模版错误,请检查文件") } for i := 0; i < len(sheetCols)-2; i++ { if i == 2 { continue } if len(sheetCols[i]) < len(sheetCols[i+1]) { return errors.New("格式错误,有必填项未录入") } for _, v := range sheetCols[i] { if v == "" { return errors.New("格式错误,有必填项未录入") } } } return nil } // 校验库存导入规则 // 导入规则: // (1)商品名称:100 字符内,商品名称和商品编号可只填一项,同时填写时优先按照商品名称导库存; // 且需为已有商品名称,否则红框展示,下方红字提示“该商品不存在,请新建商品” // (2)商品编号:限纯数字,商品名称和商品编号可只填一项,同时填写时优先按照商品名称导库存; // 且需为已有商品编号,否则红框展示,下方红字提示“该商品编号不存在” // (3)所属门店:必填项,100 字符内;且需为已有门店,否则红框展示,下方红字提示“该门店不存在,请新建门店” // (4)商品串码:100 字符内,限制仅可填数字或英文;串码类必填,非串码不填 // 如果是串码类商品没填串码或者填了中文,红框展示,下方红字提示“串码类商品,请填写正确地串码” // 如果是非串码商品,填了串码,红框展示,下方红字提示“非串码商品,无需填写串码” // 备注:库存导入时,如果是串码商品都需填写串码才能导入,不区分是否自动生成串码;自动生成是在后续采购导入时使用;2023/12/19跟产品核实 // (5)采购价:必填项,入库时的采购价,限>0 数字 // (6)员工成本价:必填项,入库时的员工成本价,限>0 数字 // (7)供应商:必填项,100 字符内,且需为已有供应商,否则红框展示,下方红字提示“该供应商不存在,请新建供应商” // (8)入库时间:非必填项,含年月日,填写后入库时间会按照填写的日期0点展示,不填写则按照实际导入成功时间展示 // (9)数量:必填项,限制≥1正整数, // 串码类商品需拆分成每个数量为1单独导入, // 非串码可大于1直接导入 * 串码类商品如数量填写>1,红框展示,下方红字提示“串码类商品数量只能为1” // (10)入库时间:必须早于/小于当前时间 func checkStockExcel(sheetCols [][]string) error { if len(sheetCols) != 9 { return errors.New("模版错误,请检查文件") } maxLength := findMaxLength(sheetCols) nLow, nMax := determineLowAndMax(sheetCols) if nMax < maxLength { return errors.New("第" + strconv.Itoa(nMax+1) + "行商品名称和商品编号不能同时为空") } // 判断是否有重复串码 if duplicateName, nFlag := hasDuplicateIMEI(sheetCols[3]); nFlag { return fmt.Errorf("商品串码不允许重复,请检查:[%v]", duplicateName) } //先遍历第1列 for i := 1; i < nLow; i++ { if sheetCols[0][i] == "" && sheetCols[1][i] == "" { // 商品名称和编号不能都为空 return errors.New("第" + strconv.Itoa(i+1) + "行商品名称和商品编号不能同时为空") } } for i := 1; i < nMax; i++ { // todo if i < len(sheetCols[0]) { if !IsExistingProduct(sheetCols[0][i]) { return errors.New("第" + strconv.Itoa(i+1) + "行商品不存在,请新建商品") } } if i < len(sheetCols[1]) { // 商品编号必须为纯数字 if sheetCols[1][i] != "" { if _, err := strconv.Atoi(sheetCols[1][i]); err != nil { return errors.New("第" + strconv.Itoa(i+1) + "行商品编号必须为纯数字") } } if !isExistingProductCode(sheetCols[1][i]) { return errors.New("第" + strconv.Itoa(i+1) + "行商品编号不存在") } } // 所属门店不能为空 if i < len(sheetCols[2]) { if sheetCols[2][i] == "" { return errors.New("第" + strconv.Itoa(i+1) + "行所属门店不能为空") } if !isExistingStore(sheetCols[2][i]) { return errors.New("第" + strconv.Itoa(i+1) + "行门店不存在,请新建门店") } } // 商品串码规则校验 当串码行数跟其他一致时正常遍历,如果小于其他行,则最后一行不遍历 todo // 如串码在商品库存表已经存在,则报错提示 //if len(sheetCols[3]) <= nLow && i+1 < nLow { if i < len(sheetCols[3]) { productName := "" if i < len(sheetCols[0]) { productName = sheetCols[0][i] } productCode := "" if i < len(sheetCols[1]) { productCode = sheetCols[1][i] } count := "" if i < len(sheetCols[8]) { count = sheetCols[8][i] } if err := checkSerialCode(productName, productCode, sheetCols[3][i], i); err != nil { return err } // 串码类商品数量只能为1 if sheetCols[3][i] != "" && count != "1" { return errors.New("第" + strconv.Itoa(i+1) + "行串码类商品数量只能为1") } } //// 采购价、员工成本价必须大于0 //if i < len(sheetCols[4]) { // if purchasePrice, err := strconv.ParseFloat(sheetCols[4][i], 64); err != nil || purchasePrice <= 0 { // return errors.New("第" + strconv.Itoa(i+1) + "行采购价必须是大于0的数字") // } //} //if i < len(sheetCols[5]) { // if employeeCost, err := strconv.ParseFloat(sheetCols[5][i], 64); err != nil || employeeCost <= 0 { // return errors.New("第" + strconv.Itoa(i+1) + "行员工成本价必须是大于0的数字") // } //} // 供应商不能为空 if i < len(sheetCols[6]) { if sheetCols[6][i] == "" { return errors.New("第" + strconv.Itoa(i+1) + "行供应商不能为空") } if !isExistingSupplier(sheetCols[6][i]) { return errors.New("第" + strconv.Itoa(i+1) + "行供应商不存在,请新建供应商") } } // 入库时间格式校验 //if len(sheetCols[7]) < nLow && i+1 < nLow { if i < len(sheetCols[7]) { if sheetCols[7][i] != "" { parsedTime, err := time.Parse("2006/1/2", sheetCols[7][i]) if err != nil { return errors.New("第" + strconv.Itoa(i+1) + "行入库时间格式错误,请设置单元格格式为日期:YYYY/MM/DD") } // 格式化时间为指定格式 formattedTime := parsedTime.Format(DateTimeFormat) //sheetCols[7][i] = formattedTime + "00-00-00" // 需小于当前时间 nFlag, _ := isInputTimeBeforeOrEqualNow(formattedTime) if !nFlag { return errors.New("第" + strconv.Itoa(i+1) + "行入库时间不能晚于当前时间") } } } // 数量必须为正整数 if i < len(sheetCols[8]) { if quantity, err := strconv.Atoi(sheetCols[8][i]); err != nil || quantity < 1 { return errors.New("第" + strconv.Itoa(i+1) + "行数量必须是大于等于1的整数") } } } return nil } // 判断是否早于等于当前时间 func isInputTimeBeforeOrEqualNow(inputTimeString string) (bool, error) { // 将输入时间字符串解析为 time.Time 类型 inputTime, _ := time.Parse(time.RFC3339, inputTimeString) // 获取当前时间 currentTime := time.Now() // 比较时间 isBeforeOrEqual := inputTime.After(currentTime) return !isBeforeOrEqual, nil } // 校验第5列门店名称是否相同 func hasDuplicateStore(sheetCols [][]string) (string, bool) { storeMap := make(map[string]struct{}) var storeName string for row := 1; row < len(sheetCols[4]); row++ { // 获取当前行的门店名称 currentStore := sheetCols[4][row] // 第5列门店名称 if currentStore == "" { continue // 如果门店名称为空,跳过该行 } // 如果这是第一次遇到门店名称,记录下来 if storeName == "" { storeName = currentStore } // 检查当前门店名称是否与之前的名称一致 if currentStore != storeName { // 找到不一致的门店名称,返回不一致的门店名称和标记为true return currentStore, true } // 存储门店名称 storeMap[currentStore] = struct{}{} } // 如果没有发现不一致的门店名称,返回空字符串和false return "", false } // 校验串码是否相同,有则false func hasDuplicateIMEI(sheetCols []string) (string, bool) { nameMap := make(map[string]struct{}) for _, name := range sheetCols { if _, exists := nameMap[name]; exists && name != "" { // 有重复的名称 return name, true } nameMap[name] = struct{}{} } // 没有重复的名称 return "", false } func findMaxLength(sheetCols [][]string) int { maxLength := 0 for _, col := range sheetCols { if len(col) > maxLength { maxLength = len(col) } } return maxLength } func determineLowAndMax(sheetCols [][]string) (int, int) { nLow := len(sheetCols[0]) nMax := len(sheetCols[1]) if len(sheetCols[0]) >= len(sheetCols[1]) { nLow = len(sheetCols[1]) nMax = len(sheetCols[0]) } return nLow, nMax } // IsExistingProduct 查询商品资料中某个名称的商品是否存在 func IsExistingProduct(productName string) bool { if productName == "" { return true } // 实现商品名称是否存在的逻辑 var count int64 orm.Eloquent.Debug().Model(&ErpCommodity{}). Where("name = ?", productName). Count(&count) return count > 0 } func isExistingProductCode(productCode string) bool { if productCode == "" { return true } // 实现商品编号是否存在的逻辑 var count int64 orm.Eloquent.Debug().Model(&ErpCommodity{}). Where("serial_number = ?", productCode). Count(&count) return count > 0 } func isExistingStore(storeName string) bool { // 实现门店是否存在的逻辑 var count int64 orm.Eloquent.Debug().Model(&Store{}). Where("name = ?", storeName). Count(&count) return count > 0 } func IsExistingStoreById(storeId int) bool { // 实现门店是否存在的逻辑 var count int64 orm.Eloquent.Debug().Model(&Store{}). Where("id = ?", storeId). Count(&count) return count > 0 } func isExistingSupplier(supplierName string) bool { // 实现供应商是否存在的逻辑 var count int64 orm.Eloquent.Debug().Model(&ErpSupplier{}). Where("name = ?", supplierName). Count(&count) return count > 0 } func checkSerialCode(productName, productCode, serialCode string, row int) error { var existingCommodity ErpCommodity var existingStockCommodity ErpStockCommodity var queryCondition string if productCode != "" { queryCondition = "serial_number= ?" } else if productName != "" { queryCondition = "name = ?" } else { return errors.New("商品名称和商品编码不能都为空") } result := orm.Eloquent.Where(queryCondition, productName).Or(queryCondition, productCode).First(&existingCommodity) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return errors.New("未查询到商品数据") } fmt.Println("Error:", result.Error) return result.Error } nIMEIType := int(existingCommodity.IMEIType) if nIMEIType == 1 && serialCode != "" { return errors.New("非串码商品,无需填写串码") } //if nIMEIType == 2 && serialCode != ""{ // return errors.New("第" + strconv.Itoa(row+1) + "行串码无需填写,该商品串码系统自动生成") //} if (nIMEIType == 2 || nIMEIType == 3) && serialCode == "" { return errors.New("第" + strconv.Itoa(row+1) + "行是串码类商品,请填写正确地串码") } if nIMEIType != 1 && !isNumericOrAlpha(serialCode) { return errors.New("串码类商品,请填写正确的串码") } if nIMEIType != NoIMEICommodity { // 串码商品才进行判断 result = orm.Eloquent.Where("imei = ?", serialCode).First(&existingStockCommodity) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { //没找到对应串码 return nil } fmt.Println("Error:", result.Error) return result.Error } return errors.New("第" + strconv.Itoa(row+1) + "行串码重复,请勿重复导入") } return nil } //func isNumericOrAlpha(s string) bool { // for _, char := range s { // if !unicode.IsDigit(char) && !unicode.IsLetter(char) { // return false // } // } // return true //} func isNumericOrAlpha(s string) bool { if utf8.RuneCountInString(s) > 100 { return false // 字符串超过100字符,返回 false } for _, char := range s { if unicode.Is(unicode.Scripts["Han"], char) { return false // 包含中文,返回 false } } return true // 不包含中文且长度满足条件,返回 true } // 将读取的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 } // ImportCategoryData 导入商品分类到数据库 // 规则:分类只有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().Table("erp_category").Where("name = ? and pid = ? and display = ? "+ "and cooperative_business_id = ?", item.FirstCategory, 0, 1, businessId). First(&firstCategory).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { firstCategory.Name = item.FirstCategory firstCategory.Pid = 0 firstCategory.Display = 1 firstCategory.CooperativeBusinessId = businessId firstCategory.ID = 0 // 如果没有符合条件的记录,则创建新记录 err = orm.Eloquent.Debug().Create(&firstCategory).Error if err != nil { return err } } else { 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 { // 判断是导入几级分类 nType := 0 if item.ThreeCategory != "" && item.SecondCategory != "" && item.FirstCategory != "" { nType = 3 } else if item.ThreeCategory == "" && item.SecondCategory != "" && item.FirstCategory != "" { nType = 2 } else if item.ThreeCategory == "" && item.SecondCategory == "" && item.FirstCategory != "" { nType = 1 } else { return "", true } switch nType { case 1: // 导入一级分类 if exists := isCategoryExists(item.FirstCategory); exists { return item.FirstCategory, true } case 2: // 导入二级分类:查看二级分类名称是否重复;查询一级分类名称跟二/三级分类是否重复 if exists := isFirstCategoryDuplicate(item.FirstCategory); exists { return item.FirstCategory, true } if exists := isCategoryExists(item.SecondCategory); exists { return item.SecondCategory, true } case 3: // 导入三级分类:查看三级分类名称是否重复;查询一级分类名称跟二/三级分类是否重复,查询二级分类跟一,三级是否重复 if exists := isFirstCategoryDuplicate(item.FirstCategory); exists { return item.FirstCategory, true } if exists := isSecondCategoryDuplicate(item.SecondCategory); exists { return item.SecondCategory, true } if exists := isCategoryExists(item.ThreeCategory); exists { return item.ThreeCategory, true } // 如果二级分类遍历后名称重复,则需判断其一级分类是否相同,如何相同则可以重复;否则不能重复 if isSecondCategoryExists(item.SecondCategory) { if !isFirstCategoryExists(item.FirstCategory) { return item.SecondCategory, true } } } } return "", false } // 查询一级分类名称跟二/三级分类是否重复 func isFirstCategoryDuplicate(categoryName string) bool { var count int64 orm.Eloquent.Debug().Model(&Category{}). Where("name = ? and pid != ?", categoryName, 0). Count(&count) return count > 0 } // 查询一级分类名称是否重复 func isFirstCategoryExists(categoryName string) bool { var count int64 orm.Eloquent.Debug().Model(&Category{}). Where("name = ? and pid = ?", categoryName, 0). Count(&count) return count > 0 } // 查询二级分类跟一,三级是否重复 func isSecondCategoryDuplicate(categoryName string) bool { var count int64 orm.Eloquent.Debug().Model(&Category{}). Where("name = ? and LENGTH(number) != ?", categoryName, 6). Count(&count) return count > 0 } // 查询二级分类是否重复 func isSecondCategoryExists(categoryName string) bool { var count int64 orm.Eloquent.Debug().Model(&Category{}). Where("name = ? and LENGTH(number) = ?", categoryName, 6). Count(&count) return count > 0 } // 判断分类是否存在 func isCategoryExists(categoryName string) bool { var count int64 orm.Eloquent.Debug().Model(&Category{}). Where("name = ?", categoryName). Count(&count) return count > 0 } // ImportCommodityData 导入商品资料 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, businessId) 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 } // 将读取的excel数据转换成CommodityExcel struct func transStockData(colsMap []map[string]interface{}) ([]StockExcel, error) { var stockInfos []StockExcel // 遍历 colsMap 进行类型断言和转换 for _, col := range colsMap { var stockInfo StockExcel // 将 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, &stockInfo); err != nil { return nil, fmt.Errorf("failed to unmarshal JSON to StockExcel: %v", err) } // 将处理后的数据追加到 commodities 中 stockInfos = append(stockInfos, stockInfo) } return stockInfos, nil } func convertToErpCommodities(data []CommodityExcel, businessId uint32) ([]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, CooperativeBusinessId: businessId}) if len(list.List) == 0 || errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("[%v]该供应商不存在,请新建供应商", item.SupplierName) } barCode := "" if item.ErpBarcode != "" { // 条码不为空则校验 barCode, err = CheckAndConvertBarcode(item.ErpBarcode) if err != nil { return nil, err } } else { // 条码为空则自动生成 barCode, err = GenerateBarcode(category.ID) if err != nil { return nil, err } } serialNumber := fmt.Sprintf("%s%04d", category.Number, getNumberOnCategory(category.ID)+int64(productCounter.GetNextProductNumber(category.Number))) fmt.Println("商品编号:", serialNumber) erpCommodity, err := convertToErpCommodity(item, category, &list.List[0], serialNumber, barCode) 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, barCode 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.ParseFloat(item.RetailPrice, 64) // 指导零售价 if err != nil { return ErpCommodity{}, fmt.Errorf("指导零售价转换有误:[%v]", err) } nMinRetailPrice, err := strconv.ParseFloat(item.MinRetailPrice, 64) // 最低零售价 if err != nil { return ErpCommodity{}, fmt.Errorf("最低零售价转换有误:[%v]", err) } nStaffCostPrice, err := strconv.ParseFloat(item.StaffCostPrice, 64) // 员工成本价加价 if err != nil { return ErpCommodity{}, fmt.Errorf("员工成本价加价转换有误:[%v]", err) } nWholesalePrice, err := strconv.ParseFloat(item.WholesalePrice, 64) // 指导采购价 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, ErpBarcode: barCode, IMEIType: nIMEIType, ErpSupplierId: supplier.ID, ErpSupplierName: item.SupplierName, RetailPrice: tool.RoundToTwoDecimalPlaces(nRetailPrice), MinRetailPrice: tool.RoundToTwoDecimalPlaces(nMinRetailPrice), StaffCostPrice: tool.RoundToTwoDecimalPlaces(nStaffCostPrice), WholesalePrice: tool.RoundToTwoDecimalPlaces(nWholesalePrice), Brokerage1: tool.RoundToTwoDecimalPlaces(brokerage1Float), Brokerage2: tool.RoundToTwoDecimalPlaces(brokerage2Float), MemberDiscount: tool.RoundToTwoDecimalPlaces(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 } //// GenerateSerialNumber 生成商品编号(会有重复编码) //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). // Total(&count) // // serialNumber := fmt.Sprintf("%s%04d", category.Number, count+1) // fmt.Println("商品编号:", serialNumber) // return serialNumber, nil //} // GenerateSerialNumber 生成商品编号 func GenerateSerialNumber(categoryId uint32) (string, error) { // 查询商品分类 var category ErpCategory if err := orm.Eloquent.Debug().Model(&ErpCategory{}). Where("id = ?", categoryId).First(&category).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return "", errors.New("未查到商品分类") } return "", err } // 查询该商品分类下最大的商品编号 var maxSerialNumber sql.NullString if err := orm.Eloquent.Debug().Model(&ErpCommodity{}). Where("erp_category_id = ?", categoryId). Select("MAX(serial_number)"). Row().Scan(&maxSerialNumber); err != nil && !errors.Is(err, sql.ErrNoRows) { return "", err } // 处理 NULL 值 var lastNumber uint64 if maxSerialNumber.Valid { lastSerial := maxSerialNumber.String lastNumberStr := lastSerial[len(category.Number):] lastNumber, _ = strconv.ParseUint(lastNumberStr, 10, 32) } // 递增生成新的商品编号 newNumber := lastNumber + 1 serialNumber := fmt.Sprintf("%s%04d", category.Number, newNumber) fmt.Println("商品编号:", serialNumber) return serialNumber, nil } // UpdateErpStockAmountInfo 更新库存和库存商品表的金额:指导零售价、最低零售价 func UpdateErpStockAmountInfo(begin *gorm.DB, req *CommodityEditRequest, barCode, serial_number string, category *ErpCategory) error { if category != nil && category.ID != 0 { // 分类信息有值 // 更新库存表 err := begin.Table("erp_stock").Where("erp_commodity_id=?", req.Id). Updates(map[string]interface{}{ "retail_price": req.RetailPrice, "min_retail_price": req.MinRetailPrice, "erp_category_id": category.ID, "erp_category_name": category.Name, "erp_commodity_name": req.Name, "commodity_serial_number": serial_number, }).Error if err != nil { return err } // 更新库存商品表 err = begin.Table("erp_stock_commodity").Where("erp_commodity_id=? and state not in (2,5)", req.Id). Updates(map[string]interface{}{ "retail_price": req.RetailPrice, "min_retail_price": req.MinRetailPrice, //"staff_cost_price": staffCostPrice, "erp_barcode": barCode, "erp_category_id": category.ID, "erp_category_name": category.Name, "erp_commodity_name": req.Name, "commodity_serial_number": serial_number, }).Error if err != nil { return err } } else { // 更新库存表 err := begin.Table("erp_stock").Where("erp_commodity_id=?", req.Id). Updates(map[string]interface{}{ "retail_price": req.RetailPrice, "min_retail_price": req.MinRetailPrice, "erp_commodity_name": req.Name, }).Error if err != nil { return err } // 更新库存商品表 err = begin.Table("erp_stock_commodity").Where("erp_commodity_id=? and state not in (2,5)", req.Id). Updates(map[string]interface{}{ "retail_price": req.RetailPrice, "min_retail_price": req.MinRetailPrice, //"staff_cost_price": staffCostPrice, "erp_barcode": barCode, "erp_commodity_name": req.Name, }).Error if err != nil { return err } } return nil } // CheckCommodityIsHavaStock 检查某个商品是否还有库存 func CheckCommodityIsHavaStock(commodityId uint32) bool { var count int64 err := orm.Eloquent.Table("erp_stock").Where("erp_commodity_id = ? and count > 0", commodityId). Count(&count).Error if err != nil { logger.Error("CheckCommodityIsHavaStock err:", logger.Field("err", err)) } return count > 0 } // IsExistingCommodity 检查是否存在某个商品 func IsExistingCommodity(commodityId uint32) bool { var count int64 err := orm.Eloquent.Table("erp_commodity").Where("id = ?", commodityId). Count(&count).Error if err != nil { logger.Error("IsExistingCommodity err:", logger.Field("err", err)) } return count > 0 } // 校验导入零售excel格式 // 必填项:商品名称、销售数量 // 商品名称和串码是否对应 // 串码商品数量只能为1 // 是否有库存 //func checkOrderExcel(sheetCols [][]string) error { // if len(sheetCols) != 8 { // return errors.New("模版错误,请检查文件") // } // // maxLength := findMaxLength(sheetCols) // nLow, nMax := determineLowAndMax(sheetCols) // // if nMax < maxLength { // return errors.New("第" + strconv.Itoa(nMax+1) + "行商品名称和商品编号不能同时为空") // } // // // 判断是否有重复串码 // if duplicateName, nFlag := hasDuplicateIMEI(sheetCols[2]); nFlag { // return fmt.Errorf("商品串码不允许重复,请检查:[%v]", duplicateName) // } // // //先遍历第1列 // for i := 1; i < nLow; i++ { // if sheetCols[0][i] == "" && sheetCols[1][i] == "" { // 商品名称和编号不能都为空 // return errors.New("第" + strconv.Itoa(i+1) + "行商品名称和商品编号不能同时为空") // } // } // // for i := 1; i < nMax; i++ { // if i < len(sheetCols[1]) { // if !IsExistingProduct(sheetCols[1][i]) { // return errors.New("第" + strconv.Itoa(i+1) + "行商品不存在") // } // } // // // 商品编号必须为纯数字 // if i < len(sheetCols[0]) { // if sheetCols[0][i] != "" { // if _, err := strconv.Atoi(sheetCols[0][i]); err != nil { // return errors.New("第" + strconv.Itoa(i+1) + "行商品编号必须为纯数字") // } // } // // if !isExistingProductCode(sheetCols[0][i]) { // return errors.New("第" + strconv.Itoa(i+1) + "行商品编号不存在") // } // } // // // 所属门店不能为空 // if i < len(sheetCols[4]) { // if sheetCols[4][i] == "" { // return errors.New("第" + strconv.Itoa(i+1) + "行所属门店不能为空") // } // if !isExistingStore(sheetCols[4][i]) { // return errors.New("第" + strconv.Itoa(i+1) + "行门店不存在") // } // } // // // 数量必须为正整数 // if i < len(sheetCols[3]) { // if quantity, err := strconv.Atoi(sheetCols[3][i]); err != nil || quantity < 1 { // return errors.New("第" + strconv.Itoa(i+1) + "行数量必须是大于等于1的整数") // } // } // // // 串码商品数量只能为1 // var nCount int // if i < len(sheetCols[3]) { // if i < len(sheetCols[3]) { // nCount, _ = strconv.Atoi(sheetCols[3][i]) // } // // // 串码类商品数量只能为1 // if sheetCols[2][i] != "" && nCount != 1 { // return errors.New("第" + strconv.Itoa(i+1) + "行串码类商品数量只能为1") // } // } // // // 判断商品是否赠送 // if i < len(sheetCols[6]) { // if sheetCols[6][i] != "是" && sheetCols[6][i] != "" { // return errors.New("第" + strconv.Itoa(i+1) + "行商品是否赠送填写有误") // } // } // // // 查询商品是否有库存 // if sheetCols[2][i] != "" { // 串码商品 // var imeiStockCommodity ErpStockCommodity // err := orm.Eloquent.Table("erp_stock_commodity").Where("imei = ? and state = ?", // sheetCols[2][i], InStock). // Find(&imeiStockCommodity).Error // if err != nil { // return err // } // if imeiStockCommodity.ID == 0 { // return errors.New("第" + strconv.Itoa(i+1) + "行商品不存在") // } // // 门店对比 // if imeiStockCommodity.StoreName != sheetCols[4][i] { // return errors.New("第" + strconv.Itoa(i+1) + "行商品" + // "[" + imeiStockCommodity.ErpCommodityName + "]非所选门店库存,请检查") // } // // 零售价对比 // var floatVal float64 // if i < len(sheetCols[5]) { // if sheetCols[5][i] != "" { // floatVal, err = strconv.ParseFloat(sheetCols[5][i], 64) // if err != nil { // return errors.New("第" + strconv.Itoa(i+1) + "行零售价有误") // } // strMin := strconv.FormatFloat(imeiStockCommodity.MinRetailPrice, 'f', 2, 64) // if floatVal < imeiStockCommodity.MinRetailPrice { // return errors.New("第" + strconv.Itoa(i+1) + "行零售价有误,不能低于最低零售价[" + strMin + "]") // } // } // } // } else { // 非串码商品 // var count int64 // var err error // if sheetCols[0][i] != "" { // 商品编号不为空 // err = orm.Eloquent.Table("erp_stock_commodity"). // Where("commodity_serial_number = ? and store_name = ? and state = ? and imei_type = ?", // sheetCols[0][i], sheetCols[4][i], InStock, NoIMEICommodity). // Count(&count).Error // } else { // err = orm.Eloquent.Table("erp_stock_commodity"). // Where("erp_commodity_name = ? and store_name = ? and state = ? and imei_type = ?", // sheetCols[1][i], sheetCols[4][i], InStock, NoIMEICommodity). // Count(&count).Error // } // if err != nil { // return errors.New("第" + strconv.Itoa(i+1) + "行商品不存在") // } // // if count < int64(nCount) { // // 获取商品名称 // return errors.New("第" + strconv.Itoa(i+1) + "行商品" + "[" + sheetCols[1][i] + "]库存不足") // } // } // } // // return nil //} func checkOrderExcel(sheetCols [][]string, storeId int) error { // 校验列数是否正确 if len(sheetCols) != 8 { return errors.New("模版错误,请检查文件") } // 获取最大行数 nMax := findMaxLength(sheetCols) if nMax == 0 { return errors.New("模版内容为空") } // 校验商品串码是否重复 if duplicateName, nFlag := hasDuplicateIMEI(sheetCols[2]); nFlag { return fmt.Errorf("商品串码不允许重复,请检查:[%v]", duplicateName) } // 判断门店名称是否有不同 if duplicateName, nFlag := hasDuplicateStore(sheetCols); nFlag { return fmt.Errorf("门店名称不同,请检查:[%v]", duplicateName) } orderStore, err := GetStore(uint32(storeId)) if err != nil { return err } if sheetCols[4][1] != orderStore.Name { return fmt.Errorf("导入数据门店[%s]与订单门店[%s]不同,请检查", sheetCols[4][1], orderStore.Name) } // 缓存商品编号、商品名称和门店查询结果 productCodeCache := make(map[string]bool) productNameCache := make(map[string]bool) storeCache := make(map[string]bool) // 预加载所有商品编号、商品名称和门店 loadAllProductsIntoCache(productCodeCache, productNameCache) loadAllStoresIntoCache(storeCache) // 通道用于并发校验 var wg sync.WaitGroup errCh := make(chan error, nMax) // 限制最大并发数量 maxConcurrency := 10 sem := make(chan struct{}, maxConcurrency) for i := 1; i < nMax; i++ { // 校验行是否越界 if !isRowValid(sheetCols, i) { continue } // 等待一个空位 sem <- struct{}{} wg.Add(1) go func(row int) { defer wg.Done() defer func() { <-sem }() // 完成任务后释放空位 if err := validateRow(sheetCols, row, productCodeCache, productNameCache, storeCache); err != nil { errCh <- fmt.Errorf("第 %d 行: %v", row+1, err) } }(i) } wg.Wait() close(errCh) // 返回第一个错误 for err := range errCh { return err } validateStock(sheetCols) return nil } // 检查第 row 行是否所有列都有有效数据 func isRowValid(sheetCols [][]string, row int) bool { for _, col := range sheetCols { if row >= len(col) { // 行数超出当前列的长度 return false } } return true } // 批量加载商品信息到缓存 func loadAllProductsIntoCache(productCodeCache, productNameCache map[string]bool) { var products []ErpCommodity orm.Eloquent.Debug().Find(&products) for _, product := range products { productCodeCache[product.SerialNumber] = true productNameCache[product.Name] = true } } // 批量加载门店信息到缓存 func loadAllStoresIntoCache(storeCache map[string]bool) { var stores []Store orm.Eloquent.Debug().Find(&stores) for _, store := range stores { storeCache[store.Name] = true } } // 校验单行数据 func validateRow(sheetCols [][]string, row int, productCodeCache, productNameCache, storeCache map[string]bool) error { fmt.Println("row is:", row) // 校验商品名称和商品编号 if sheetCols[0][row] == "" && sheetCols[1][row] == "" { return errors.New("商品名称和商品编号不能同时为空") } // 校验商品编号是否存在 if sheetCols[0][row] != "" { if _, err := strconv.Atoi(sheetCols[0][row]); err != nil { return errors.New("商品编号必须为纯数字") } if !productCodeCache[sheetCols[0][row]] { return errors.New("商品编号不存在") } } // 校验商品名称是否存在 if sheetCols[1][row] != "" && !productNameCache[sheetCols[1][row]] { return errors.New("商品名称不存在") } // 校验所属门店是否存在 if sheetCols[4][row] == "" || !storeCache[sheetCols[4][row]] { return errors.New("所属门店不能为空或不存在") } // 校验数量是否为正整数 if quantity, err := strconv.Atoi(sheetCols[3][row]); err != nil || quantity < 1 { return errors.New("数量必须是大于等于1的整数") } // 校验串码商品数量只能为1 if sheetCols[2][row] != "" && sheetCols[3][row] != "1" { return errors.New("串码类商品数量只能为1") } // 校验商品是否赠送 if sheetCols[6][row] != "" && sheetCols[6][row] != "是" { return errors.New("商品是否赠送填写有误") } return nil } // validateStock 校验库存信息 func validateStock(sheetCols [][]string) error { // 预加载所有库存数据,按门店 store_id 过滤 var imeiStockCommodities []ErpStockCommodity err := orm.Eloquent.Table("erp_stock_commodity"). Where("state = ? AND store_name = ?", InStock, sheetCols[4][1]). Find(&imeiStockCommodities).Error if err != nil { return fmt.Errorf("加载库存数据失败: %v", err) } // 构建库存缓存:按 imei 和非串码商品分组 imeiStockMap := make(map[string]ErpStockCommodity) // 串码商品缓存 nonIMEIStockMap := make(map[string]map[string]int64) // 非串码商品库存,按 store -> commodity 分组 for _, stock := range imeiStockCommodities { if stock.IMEI != "" { imeiStockMap[stock.IMEI] = stock } else { if _, exists := nonIMEIStockMap[stock.StoreName]; !exists { nonIMEIStockMap[stock.StoreName] = make(map[string]int64) } key := stock.CommoditySerialNumber if key == "" { key = stock.ErpCommodityName } nonIMEIStockMap[stock.StoreName][key]++ } } // 校验库存信息 for row := 1; row < len(sheetCols[0]); row++ { if sheetCols[2][row] != "" { // 串码商品 stock, exists := imeiStockMap[sheetCols[2][row]] if !exists { return fmt.Errorf("第 %d 行: 串码商品不存在或无库存", row+1) } if stock.StoreName != sheetCols[4][row] { return fmt.Errorf("第 %d 行: 商品[%s]非所选门店库存,请检查", row+1, stock.ErpCommodityName) } if sheetCols[5][row] != "" { price, err := strconv.ParseFloat(sheetCols[5][row], 64) if err != nil || price < stock.MinRetailPrice { return fmt.Errorf("第 %d 行: 零售价有误,不能低于最低零售价[%.2f]", row+1, stock.MinRetailPrice) } } } else { // 非串码商品 storeName := sheetCols[4][row] commodityKey := sheetCols[0][row] if commodityKey == "" { commodityKey = sheetCols[1][row] } storeStock, storeExists := nonIMEIStockMap[storeName] if !storeExists || storeStock[commodityKey] < 1 { return fmt.Errorf("第 %d 行: 商品[%s]库存不足", row+1, sheetCols[1][row]) } } } return nil } //// 校验库存信息 //func validateStock(sheetCols [][]string, row int) error { // if sheetCols[2][row] != "" { // 串码商品 // var imeiStockCommodity ErpStockCommodity // err := orm.Eloquent.Table("erp_stock_commodity").Where("imei = ? AND state = ?", // sheetCols[2][row], InStock).Find(&imeiStockCommodity).Error // if err != nil || imeiStockCommodity.ID == 0 { // return errors.New("串码商品不存在或无库存") // } // if imeiStockCommodity.StoreName != sheetCols[4][row] { // return fmt.Errorf("商品[%s]非所选门店库存,请检查", imeiStockCommodity.ErpCommodityName) // } // if sheetCols[5][row] != "" { // if price, err := strconv.ParseFloat(sheetCols[5][row], 64); err != nil || price < imeiStockCommodity.MinRetailPrice { // return fmt.Errorf("零售价有误,不能低于最低零售价[%.2f]", imeiStockCommodity.MinRetailPrice) // } // } // } else { // 非串码商品 // var count int64 // err := orm.Eloquent.Table("erp_stock_commodity"). // Where("(commodity_serial_number = ? OR erp_commodity_name = ?) AND store_name = ? AND state = ? AND imei_type = ?", // sheetCols[0][row], sheetCols[1][row], sheetCols[4][row], InStock, NoIMEICommodity). // Count(&count).Error // if err != nil || count < 1 { // return fmt.Errorf("商品[%s]库存不足", sheetCols[1][row]) // } // } // // return nil //} // 将读取的excel数据转换成OrderExcel struct func transOrderData(colsMap []map[string]interface{}) ([]OrderExcel, error) { var stockInfos []OrderExcel // 遍历 colsMap 进行类型断言和转换 for _, col := range colsMap { var stockInfo OrderExcel // 将 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, &stockInfo); err != nil { return nil, fmt.Errorf("failed to unmarshal JSON to StockExcel: %v", err) } // 将处理后的数据追加到 commodities 中 stockInfos = append(stockInfos, stockInfo) } return stockInfos, nil } func ImportOrderData(colsMap []map[string]interface{}) ([]ErpOrderCommodity, error) { list, err := transOrderData(colsMap) if err != nil { return nil, err } var orderCommodities []ErpOrderCommodity for i, _ := range list { var orderCommodity ErpOrderCommodity // 商品数量 var nCount int nCount, _ = strconv.Atoi(list[i].Count) // 判断商品是否赠送 var nPresentType uint32 nPresentType = 1 if i < len(list[i].PresentType) { if list[i].PresentType == "是" { nPresentType = 2 } } // 查询商品是否有库存 if list[i].IMEI != "" { // 串码商品 var imeiStockCommodity ErpStockCommodity err = orm.Eloquent.Table("erp_stock_commodity").Where("imei = ? and state = ?", list[i].IMEI, InStock). Find(&imeiStockCommodity).Error if err != nil { return nil, err } if imeiStockCommodity.ID == 0 { return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在") } // 门店对比 if imeiStockCommodity.StoreName != list[i].StoreName { return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品" + "[" + imeiStockCommodity.ErpCommodityName + "]非所选门店库存,请检查") } // 零售价对比 var floatVal float64 if list[i].SalePrice != "" { floatVal, err = strconv.ParseFloat(list[i].SalePrice, 64) if err != nil { return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误") } strMin := strconv.FormatFloat(imeiStockCommodity.MinRetailPrice, 'f', 2, 64) if floatVal < imeiStockCommodity.MinRetailPrice { return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误,不能低于最低零售价[" + strMin + "]") } } else { floatVal = imeiStockCommodity.RetailPrice } orderCommodity.ErpCategoryId = imeiStockCommodity.ErpCategoryId orderCommodity.ErpCategoryName = imeiStockCommodity.ErpCategoryName orderCommodity.ErpCommodityId = imeiStockCommodity.ErpCommodityId orderCommodity.ErpCommodityName = imeiStockCommodity.ErpCommodityName orderCommodity.ErpSupplierId = imeiStockCommodity.ErpSupplierId orderCommodity.ErpSupplierName = imeiStockCommodity.ErpSupplierName orderCommodity.IMEIType = imeiStockCommodity.IMEIType orderCommodity.IMEI = imeiStockCommodity.IMEI orderCommodity.PresentType = nPresentType orderCommodity.RetailPrice = imeiStockCommodity.RetailPrice orderCommodity.SalePrice = floatVal orderCommodity.ReceivedAmount = floatVal orderCommodity.SaleDiscount = imeiStockCommodity.RetailPrice - floatVal orderCommodity.WholesalePrice = imeiStockCommodity.WholesalePrice orderCommodity.StaffCostPrice = imeiStockCommodity.StaffCostPrice orderCommodity.MemberDiscount = imeiStockCommodity.MemberDiscount orderCommodity.SalesProfit = floatVal - imeiStockCommodity.WholesalePrice orderCommodity.StaffProfit = floatVal - imeiStockCommodity.StaffCostPrice orderCommodity.Count = int32(nCount) orderCommodity.Remark = list[i].Remark } else { // 非串码商品 var count int64 var imeiStockCommodities []ErpStockCommodity if list[i].SerialNum != "" { // 商品编号不为空 err = orm.Eloquent.Table("erp_stock_commodity"). Where("commodity_serial_number = ? and store_name = ? and state = ? and imei_type = ?", list[i].SerialNum, list[i].StoreName, InStock, NoIMEICommodity). Count(&count).Error } else { err = orm.Eloquent.Table("erp_stock_commodity"). Where("erp_commodity_name = ? and store_name = ? and state = ? and imei_type = ?", list[i].Name, list[i].StoreName, InStock, NoIMEICommodity). Count(&count).Error } if err != nil { return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在") } if count < int64(nCount) { // 获取商品名称 return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品" + "[" + list[i].Name + "]库存不足") } if list[i].SerialNum != "" { // 商品编号不为空 err = orm.Eloquent.Table("erp_stock_commodity"). Where("commodity_serial_number = ? and store_name = ? and state = ? and imei_type = ?", list[i].SerialNum, list[i].StoreName, InStock, NoIMEICommodity). Find(&imeiStockCommodities).Order("first_stock_time DESC").Error } else { err = orm.Eloquent.Table("erp_stock_commodity"). Where("erp_commodity_name = ? and store_name = ? and state = ? and imei_type = ?", list[i].Name, list[i].StoreName, InStock, NoIMEICommodity). Find(&imeiStockCommodities).Order("first_stock_time DESC").Error } if err != nil { return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在") } if len(imeiStockCommodities) == 0 { return nil, errors.New("第" + strconv.Itoa(i+1) + "行商品不存在") } // 零售价对比 var floatVal float64 if list[i].SalePrice != "" { floatVal, err = strconv.ParseFloat(list[i].SalePrice, 64) if err != nil { return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误") } strMin := strconv.FormatFloat(imeiStockCommodities[0].MinRetailPrice, 'f', 2, 64) if floatVal < imeiStockCommodities[0].MinRetailPrice { return nil, errors.New("第" + strconv.Itoa(i+1) + "行零售价有误,不能低于最低零售价[" + strMin + "]") } } else { floatVal = imeiStockCommodities[0].RetailPrice } orderCommodity.ErpCategoryId = imeiStockCommodities[0].ErpCategoryId orderCommodity.ErpCategoryName = imeiStockCommodities[0].ErpCategoryName orderCommodity.ErpCommodityId = imeiStockCommodities[0].ErpCommodityId orderCommodity.ErpCommodityName = imeiStockCommodities[0].ErpCommodityName orderCommodity.ErpSupplierId = imeiStockCommodities[0].ErpSupplierId orderCommodity.ErpSupplierName = imeiStockCommodities[0].ErpSupplierName orderCommodity.IMEIType = imeiStockCommodities[0].IMEIType orderCommodity.IMEI = imeiStockCommodities[0].IMEI orderCommodity.PresentType = nPresentType orderCommodity.RetailPrice = imeiStockCommodities[0].RetailPrice orderCommodity.SalePrice = floatVal orderCommodity.ReceivedAmount = floatVal orderCommodity.SaleDiscount = imeiStockCommodities[0].RetailPrice - floatVal orderCommodity.WholesalePrice = imeiStockCommodities[0].WholesalePrice orderCommodity.StaffCostPrice = imeiStockCommodities[0].StaffCostPrice orderCommodity.MemberDiscount = imeiStockCommodities[0].MemberDiscount orderCommodity.SalesProfit = floatVal - imeiStockCommodities[0].WholesalePrice orderCommodity.StaffProfit = floatVal - imeiStockCommodities[0].StaffCostPrice orderCommodity.Count = int32(nCount) orderCommodity.Remark = list[i].Remark } orderCommodities = append(orderCommodities, orderCommodity) } return orderCommodities, nil }