mh_goadmin_server/app/admin/models/file.go

607 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

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