mh_goadmin_server/app/admin/models/file.go

1969 lines
63 KiB
Go
Raw Normal View History

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"
2024-02-23 10:06:21 +00:00
"go-admin/logger"
tool "go-admin/tools"
"gorm.io/gorm"
"reflect"
"strconv"
"strings"
"sync"
2023-11-23 12:38:11 +00:00
"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"` // 备注
}
2023-11-23 12:38:11 +00:00
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
}
2023-11-15 10:17:35 +00:00
// 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{})
2023-11-23 12:38:11 +00:00
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] = ""
}
}
2024-02-23 10:06:21 +00:00
logger.Info("columnMap:", logger.Field("columnMap", columnMap))
colsMap = append(colsMap, columnMap)
}
2024-02-23 10:06:21 +00:00
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
}
2023-11-15 10:17:35 +00:00
// 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 {
2023-11-23 12:38:11 +00:00
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{})
2023-11-23 12:38:11 +00:00
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] = ""
}
}
2024-02-23 10:06:21 +00:00
logger.Info("columnMap:", logger.Field("columnMap", columnMap))
colsMap = append(colsMap, columnMap)
}
2024-02-23 10:06:21 +00:00
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有23级但没有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
}
2024-03-27 10:01:38 +00:00
//// 校验名称是否相同有则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) {
2024-03-27 10:01:38 +00:00
//firstColMap := make(map[string]bool)
secondColMap := make(map[string]string) // Map记录第二列数据的重复情况以及对应第一列的数据
thirdColMap := make(map[string]bool)
2024-03-27 10:01:38 +00:00
// 确保每一列数据都有相同数量的行数
rowsCount := len(sheetCols[0])
//for i, col := range sheetCols {
// if len(col) != rowsCount && i != 2 {
// return "每列数据的行数不一致", true
// }
//}
2024-03-27 10:01:38 +00:00
for i := 0; i < rowsCount; i++ {
//// 检查第一列
//firstColName := sheetCols[0][i]
//if firstColName != "" {
// if _, exists := firstColMap[firstColName]; exists {
// return firstColName, true
// }
// firstColMap[firstColName] = true
//}
// 检查第二列
2024-04-01 03:00:22 +00:00
if len(sheetCols[1]) < i+1 {
continue
}
2024-03-27 10:01:38 +00:00
secondColName := sheetCols[1][i]
if secondColName != "" {
if firstCol, exists := secondColMap[secondColName]; exists && firstCol != sheetCols[0][i] {
return secondColName, true
}
secondColMap[secondColName] = sheetCols[0][i]
}
// 检查第三列
2024-04-01 03:00:22 +00:00
if len(sheetCols[2]) < i+1 {
continue
}
2024-03-27 10:01:38 +00:00
thirdColName := sheetCols[2][i]
if thirdColName != "" {
if _, exists := thirdColMap[thirdColName]; exists {
return thirdColName, true
}
2024-03-27 10:01:38 +00:00
thirdColMap[thirdColName] = true
}
}
2024-03-27 10:01:38 +00:00
// 没有重复的名称
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
}
2023-11-23 12:38:11 +00:00
// 校验库存导入规则
// 导入规则:
// (1)商品名称100 字符内,商品名称和商品编号可只填一项,同时填写时优先按照商品名称导库存;
// 且需为已有商品名称,否则红框展示,下方红字提示“该商品不存在,请新建商品”
// (2)商品编号:限纯数字,商品名称和商品编号可只填一项,同时填写时优先按照商品名称导库存;
// 且需为已有商品编号,否则红框展示,下方红字提示“该商品编号不存在”
// (3)所属门店必填项100 字符内;且需为已有门店,否则红框展示,下方红字提示“该门店不存在,请新建门店”
// (4)商品串码100 字符内,限制仅可填数字或英文;串码类必填,非串码不填
// 如果是串码类商品没填串码或者填了中文,红框展示,下方红字提示“串码类商品,请填写正确地串码”
// 如果是非串码商品,填了串码,红框展示,下方红字提示“非串码商品,无需填写串码”
// 备注库存导入时如果是串码商品都需填写串码才能导入不区分是否自动生成串码自动生成是在后续采购导入时使用2023/12/19跟产品核实
2023-11-23 12:38:11 +00:00
// (5)采购价必填项入库时的采购价0 数字
// (6)员工成本价必填项入库时的员工成本价0 数字
// (7)供应商必填项100 字符内,且需为已有供应商,否则红框展示,下方红字提示“该供应商不存在,请新建供应商”
// (8)入库时间非必填项含年月日填写后入库时间会按照填写的日期0点展示不填写则按照实际导入成功时间展示
// (9)数量必填项限制≥1正整数
// 串码类商品需拆分成每个数量为1单独导入
// 非串码可大于1直接导入 * 串码类商品如数量填写1红框展示下方红字提示“串码类商品数量只能为1”
// 10入库时间必须早于/小于当前时间
2023-11-23 12:38:11 +00:00
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)
}
2023-11-23 12:38:11 +00:00
//先遍历第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) + "行商品不存在,请新建商品")
}
2023-11-23 12:38:11 +00:00
}
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) + "行商品编号必须为纯数字")
}
2023-11-23 12:38:11 +00:00
}
if !isExistingProductCode(sheetCols[1][i]) {
return errors.New("第" + strconv.Itoa(i+1) + "行商品编号不存在")
}
2023-11-23 12:38:11 +00:00
}
// 所属门店不能为空
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) + "行门店不存在,请新建门店")
}
2023-11-23 12:38:11 +00:00
}
// 商品串码规则校验 当串码行数跟其他一致时正常遍历,如果小于其他行,则最后一行不遍历 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 {
2023-11-23 12:38:11 +00:00
return err
}
// 串码类商品数量只能为1
if sheetCols[3][i] != "" && count != "1" {
2023-11-23 12:38:11 +00:00
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的数字")
// }
//}
2023-11-23 12:38:11 +00:00
//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的数字")
// }
//}
2023-11-23 12:38:11 +00:00
// 供应商不能为空
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) + "行供应商不存在,请新建供应商")
}
2023-11-23 12:38:11 +00:00
}
// 入库时间格式校验
//if len(sheetCols[7]) < nLow && i+1 < nLow {
if i < len(sheetCols[7]) {
2023-11-23 12:38:11 +00:00
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")
2023-11-23 12:38:11 +00:00
}
// 格式化时间为指定格式
formattedTime := parsedTime.Format(DateTimeFormat)
//sheetCols[7][i] = formattedTime + "00-00-00"
// 需小于当前时间
nFlag, _ := isInputTimeBeforeOrEqualNow(formattedTime)
if !nFlag {
return errors.New("第" + strconv.Itoa(i+1) + "行入库时间不能晚于当前时间")
}
2023-11-23 12:38:11 +00:00
}
}
// 数量必须为正整数
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的整数")
}
2023-11-23 12:38:11 +00:00
}
}
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
}
2023-11-23 12:38:11 +00:00
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 {
2023-11-23 12:38:11 +00:00
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
}
2023-11-23 12:38:11 +00:00
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 {
2023-11-23 12:38:11 +00:00
var existingCommodity ErpCommodity
var existingStockCommodity ErpStockCommodity
2023-11-23 12:38:11 +00:00
var queryCondition string
if productCode != "" {
queryCondition = "serial_number= ?"
} else if productName != "" {
2023-11-23 12:38:11 +00:00
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) {
2023-11-23 12:38:11 +00:00
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
2023-11-23 12:38:11 +00:00
}
//func isNumericOrAlpha(s string) bool {
// for _, char := range s {
// if !unicode.IsDigit(char) && !unicode.IsLetter(char) {
// return false
// }
// }
// return true
//}
2023-11-23 12:38:11 +00:00
func isNumericOrAlpha(s string) bool {
if utf8.RuneCountInString(s) > 100 {
return false // 字符串超过100字符返回 false
}
2023-11-23 12:38:11 +00:00
for _, char := range s {
if unicode.Is(unicode.Scripts["Han"], char) {
return false // 包含中文,返回 false
2023-11-23 12:38:11 +00:00
}
}
return true // 不包含中文且长度满足条件,返回 true
2023-11-23 12:38:11 +00:00
}
// 将读取的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
}
2023-11-15 10:17:35 +00:00
// 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
}
// 插入或获取一级分类
2024-04-01 03:00:22 +00:00
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 {
2024-04-01 03:00:22 +00:00
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 {
2024-03-27 10:01:38 +00:00
// 判断是导入几级分类
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
}
2024-03-27 10:01:38 +00:00
switch nType {
case 1: // 导入一级分类
if exists := isCategoryExists(item.FirstCategory); exists {
return item.FirstCategory, true
}
2024-04-01 03:00:22 +00:00
case 2: // 导入二级分类:查看二级分类名称是否重复;查询一级分类名称跟二/三级分类是否重复
if exists := isFirstCategoryDuplicate(item.FirstCategory); exists {
return item.FirstCategory, true
}
2024-03-27 10:01:38 +00:00
if exists := isCategoryExists(item.SecondCategory); exists {
return item.SecondCategory, true
}
2024-04-01 03:00:22 +00:00
case 3: // 导入三级分类:查看三级分类名称是否重复;查询一级分类名称跟二/三级分类是否重复,查询二级分类跟一,三级是否重复
if exists := isFirstCategoryDuplicate(item.FirstCategory); exists {
return item.FirstCategory, true
}
if exists := isSecondCategoryDuplicate(item.SecondCategory); exists {
return item.SecondCategory, true
}
2024-03-27 10:01:38 +00:00
if exists := isCategoryExists(item.ThreeCategory); exists {
return item.ThreeCategory, true
}
// 如果二级分类遍历后名称重复,则需判断其一级分类是否相同,如何相同则可以重复;否则不能重复
if isSecondCategoryExists(item.SecondCategory) {
if !isFirstCategoryExists(item.FirstCategory) {
return item.SecondCategory, true
}
}
}
}
return "", false
}
2024-04-01 03:00:22 +00:00
// 查询一级分类名称跟二/三级分类是否重复
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
}
2024-04-01 03:00:22 +00:00
// 查询二级分类跟一,三级是否重复
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).
2024-04-01 03:00:22 +00:00
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
}
2023-11-15 10:17:35 +00:00
// 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
}
2023-11-23 12:38:11 +00:00
// 将读取的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
//}
2023-11-15 10:17:35 +00:00
// 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 {
2024-03-27 10:01:38 +00:00
if category != nil && category.ID != 0 { // 分类信息有值
// 更新库存表
err := begin.Table("erp_stock").Where("erp_commodity_id=?", req.Id).
2024-03-27 10:01:38 +00:00
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,
2024-03-27 10:01:38 +00:00
}).Error
if err != nil {
return err
}
2024-03-27 10:01:38 +00:00
// 更新库存商品表
err = begin.Table("erp_stock_commodity").Where("erp_commodity_id=? and state not in (2,5)", req.Id).
2024-03-27 10:01:38 +00:00
Updates(map[string]interface{}{
"retail_price": req.RetailPrice,
"min_retail_price": req.MinRetailPrice,
2024-03-27 10:01:38 +00:00
//"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,
2024-03-27 10:01:38 +00:00
}).Error
if err != nil {
return err
}
} else {
// 更新库存表
err := begin.Table("erp_stock").Where("erp_commodity_id=?", req.Id).
2024-03-27 10:01:38 +00:00
Updates(map[string]interface{}{
"retail_price": req.RetailPrice,
"min_retail_price": req.MinRetailPrice,
"erp_commodity_name": req.Name,
2024-03-27 10:01:38 +00:00
}).Error
if err != nil {
return err
}
2024-03-27 10:01:38 +00:00
// 更新库存商品表
err = begin.Table("erp_stock_commodity").Where("erp_commodity_id=? and state not in (2,5)", req.Id).
2024-03-27 10:01:38 +00:00
Updates(map[string]interface{}{
"retail_price": req.RetailPrice,
"min_retail_price": req.MinRetailPrice,
2024-03-27 10:01:38 +00:00
//"staff_cost_price": staffCostPrice,
"erp_barcode": barCode,
"erp_commodity_name": req.Name,
2024-03-27 10:01:38 +00:00
}).Error
if err != nil {
return err
}
}
return nil
}
2024-02-23 10:06:21 +00:00
// 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
}