package basic import ( "encoding/json" "errors" "fmt" "github.com/gin-gonic/gin" "go-admin/app/admin/middleware" "go-admin/app/admin/models" orm "go-admin/common/global" "go-admin/logger" "go-admin/tools/app" "gorm.io/gorm" "io" "net/http" "strconv" ) // CommodityCreate 新增商品 // @Summary 新增商品 // @Tags 商品资料 // @Produce json // @Accept json // @Param request body models.CommodityCreateAndEditRequest true "商品新增模型" // @Success 200 {object} models.ErpCommodity // @Router /api/v1/commodity/create [post] // 规则:商品名称/商品编号相同则为同一商品,创建的时候由于商品编号不重复,无需判断 func CommodityCreate(c *gin.Context) { var req = new(models.CommodityCreateAndEditRequest) err := c.ShouldBindJSON(&req) if err != nil { fmt.Println(err.Error()) app.Error(c, http.StatusBadRequest, errors.New("param err"), "参数错误:"+err.Error()) return } // 小程序商品入参校验 if req.IsSyncedToMall == 1 { if req.GoodsAccountNum == 0 { app.Error(c, http.StatusBadRequest, errors.New("goods_account_num empty"), "收款账户不能为空") return } if req.PriceOriginal == 0 { app.Error(c, http.StatusBadRequest, errors.New("price_original empty"), "市场价不能为空") return } else { req.PriceOriginal = req.PriceOriginal * 100 } if req.PriceRm == 0 { req.PriceRm = req.PriceOriginal } else { req.PriceRm = req.PriceRm * 100 } // CatId 必须存在于 GoodsCat 表 var goodsCat models.GoodsCat if err := orm.Eloquent.Model(&models.GoodsCat{}).Where("id = ?", req.CatId).Find(&goodsCat).Error; err != nil { app.Error(c, http.StatusInternalServerError, err, "查询商品分类失败") return } if goodsCat.ID == 0 { app.Error(c, http.StatusBadRequest, errors.New("cat_id invalid"), "商品分类不存在") return } if goodsCat.Pid == 0 { app.Error(c, http.StatusBadRequest, errors.New("cat_id invalid"), "小程序商城分类错误,不能选一级分类") return } } barCode := "" if req.ErpBarcode != "" { // 条码不为空则校验 barCode, err = models.CheckAndConvertBarcode(req.ErpBarcode) if err != nil { app.Error(c, http.StatusBadRequest, errors.New("param err"), "参数错误:"+err.Error()) return } } else { // 条码为空则自动生成 barCode, err = models.GenerateBarcode(req.ErpCategoryId) if err != nil { app.Error(c, http.StatusBadRequest, errors.New("gen barcode err"), err.Error()) return } } brokerage1Float := req.Brokerage1 brokerage2Float := req.Brokerage2 memberDiscountFloat := req.MemberDiscount if models.IsExistingProduct(req.Name) { app.Error(c, http.StatusBadRequest, errors.New("param err"), "商品名称已存在,不能重复") return } if req.IsIMEI == 2 { // 是否串码:1-串码类 2-非串码 req.IMEIType = 1 // 系统生成串码:2-是(系统生成) 3-否(手动添加) 1表示非串码 } // 开启事务 tx := orm.Eloquent.Begin() if tx.Error != nil { app.Error(c, http.StatusInternalServerError, tx.Error, "开启事务失败") return } defer func() { if r := recover(); r != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, errors.New("panic"), fmt.Sprintf("事务异常: %v", r)) } }() // (1)如果需要同步到小程序商城,先创建 Goods var goodsId uint32 if req.IsSyncedToMall == 1 { goodsId, err = CreateMallGoods(tx, req) if err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, err.Error()) return } } // (2) 创建 ERP 商品,回写 GoodsId commodity := &models.ErpCommodity{ Number: 1, Name: req.Name, ErpCategoryId: req.ErpCategoryId, ErpCategoryName: "", ErpBarcode: barCode, IMEIType: req.IMEIType, ErpSupplierId: req.ErpSupplierId, ErpSupplierName: "", RetailPrice: req.RetailPrice, MinRetailPrice: req.MinRetailPrice, //StaffCostPrice: req.StaffCostPrice + req.WholesalePrice, StaffCostPrice: req.StaffCostPrice, WholesalePrice: req.WholesalePrice, Brokerage1: brokerage1Float, Brokerage2: brokerage2Float, MemberDiscount: memberDiscountFloat, Origin: req.Origin, Remark: req.Remark, Img: req.Img, StopPurchase: req.StopPurchase, ManufacturerCode: req.ManufacturerCode, IsSyncedToMall: req.IsSyncedToMall, GoodsId: goodsId, SaleStatus: 2, } err = commodity.SetErpCategory() if err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, "创建失败") return } err = commodity.IdInit() if err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, "创建失败:"+err.Error()) return } commodity.SerialNumber, err = models.GenerateSerialNumber(req.ErpCategoryId) if err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, err.Error()) return } err = tx.Create(commodity).Error if err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, "创建失败") return } // (3) 更新 Goods.ErpCommodityId if req.IsSyncedToMall == 1 { if err = tx.Model(&models.Goods{}). Where("goods_id = ?", goodsId). Update("erp_commodity_id", commodity.ID).Error; err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, "更新商城商品编号失败") return } } // (4) 提交事务 if err = tx.Commit().Error; err != nil { app.Error(c, http.StatusInternalServerError, err, "提交事务失败") return } commodity.CatId = req.CatId commodity.GoodsAccountNum = req.GoodsAccountNum commodity.PriceOriginal = req.PriceOriginal commodity.PriceRm = req.PriceRm commodity.Detail = req.Detail app.OK(c, commodity, "OK") return } // CreateMallGoods 创建小程序商城商品及默认SKU func CreateMallGoods(tx *gorm.DB, req *models.CommodityCreateAndEditRequest) (uint32, error) { // 创建规格值(SpecValue) specListStr, specValueID, err := BuildAndCreateSpecValue(tx, req.Name) if err != nil { return 0, fmt.Errorf("创建规格值失败: %w", err) } goodsSerialNo := models.CreateGoodsSerialNo() goodsId := models.CreateGoodsId() // 默认折扣配置 defaultDiscount := map[string]int{ "gold": 100, "platinum": 100, "black_gold": 100, } discountBytes, _ := json.Marshal(defaultDiscount) goods := &models.Goods{ GoodsId: goodsId, SerialNo: goodsSerialNo, CatId: req.CatId, Name: req.Name, MainImage: req.Img, Images: req.Img, Detail: req.Detail, SaleStatus: 2, // 默认下架 PriceOriginal: req.PriceOriginal, PriceRm: req.PriceRm, GoodsAccountNum: req.GoodsAccountNum, SpecList: specListStr, SpecIndex: strconv.Itoa(int(specValueID)), DiscountList: string(discountBytes), } // 创建商城商品 if err := tx.Create(goods).Error; err != nil { return 0, fmt.Errorf("创建商城商品失败: %w", err) } // 创建默认 SKU if err := CreateDefaultSKU(tx, goods, req.PriceOriginal, req.PriceRm); err != nil { return 0, fmt.Errorf("创建商品 SKU 失败: %w", err) } return goodsId, nil } // BuildAndCreateSpecValue 生成并创建规格值 func BuildAndCreateSpecValue(tx *gorm.DB, productName string) (specListStr string, specValueID uint32, err error) { // 1. 创建规格 Spec spec := models.Spec{ DisplayName: productName, Name: "", State: 2, Sort: 0, Values: []models.SpecValue{}, } if err := tx.Create(&spec).Error; err != nil { return "", 0, err } // 2. 创建规格值 SpecValue specValue := models.SpecValue{ SpecId: spec.ID, SpecDisplayName: spec.DisplayName, SpecName: spec.Name, DisplayValue: productName, State: 2, Value: "", SpecSort: 0, Sort: 1, } if err := tx.Create(&specValue).Error; err != nil { return "", 0, err } specValueID = spec.ID // 3. 构造 spec_list JSON type SpecWithValues struct { Spec models.Spec `json:"spec"` Values []models.SpecValue `json:"values"` } //spec.Values = append(spec.Values, specValue) specList := []SpecWithValues{ { Spec: spec, Values: []models.SpecValue{specValue}, }, } data, err := json.Marshal(specList) if err != nil { return "", 0, err } return string(data), specValueID, nil } // CreateDefaultSKU 创建默认 SKU func CreateDefaultSKU(tx *gorm.DB, goods *models.Goods, retailPrice, rmPrice uint32) error { // 先解析 goods.SpecList var specItems []struct { Spec interface{} `json:"spec"` Values []map[string]interface{} `json:"values"` } if err := json.Unmarshal([]byte(goods.SpecList), &specItems); err != nil { return fmt.Errorf("failed to unmarshal SpecList: %w", err) } // 拍平所有 values var specValues []map[string]interface{} for _, item := range specItems { if len(item.Values) > 0 { specValues = append(specValues, item.Values...) } } // 转回 json 存储到 attribute.SpecValueList specValuesBytes, err := json.Marshal(specValues) if err != nil { return fmt.Errorf("failed to marshal spec values: %w", err) } attribute := &models.GoodsAttribute{ GoodsId: goods.GoodsId, SerialNo: goods.SerialNo, CatId: goods.CatId, Name: goods.Name, Title: goods.Title, MainImage: goods.MainImage, SpecValueIndex: goods.SpecIndex, SpecValueList: string(specValuesBytes), // ✅ 已拍平的 values PriceOriginal: retailPrice, DealType: 2, } combo := models.GoodsAttributeCombo{ GoodsId: goods.GoodsId, SerialNo: goods.SerialNo, CatId: goods.CatId, Name: goods.Name, Title: goods.Title, MainImage: goods.MainImage, PriceRm: rmPrice, PriceVm: 0, ComboName: goods.Name, } attribute.Combos = []models.GoodsAttributeCombo{combo} if err := tx.Create(attribute).Error; err != nil { return err } combo.GoodsAttributeId = attribute.ID if err := tx.Create(&combo).Error; err != nil { return err } return nil } // UpdateMallGoodsName 根据 goodsId 更新商品相关表的名称 func UpdateMallGoodsName(tx *gorm.DB, goodsId uint32, req *models.CommodityCreateAndEditRequest) error { if goodsId == 0 { return errors.New("goodsId 不能为空") } if req.Name == "" { return errors.New("新名称不能为空") } // 先查出 Goods,获取 specIndex var goods models.Goods if err := tx.Model(&models.Goods{}). Where("goods_id = ?", goodsId). First(&goods).Error; err != nil { return fmt.Errorf("查询 Goods 失败: %w", err) } specIndex := goods.SpecIndex // 1. 更新 Goods 表 if err := tx.Model(&models.Goods{}). Where("goods_id = ?", goodsId). Updates(map[string]interface{}{ "name": req.Name, "cat_id": req.CatId, "goods_account_num": req.GoodsAccountNum, "price_original": req.PriceOriginal, "price_rm": req.PriceRm, "detail": req.Detail, "main_image": req.Img, }).Error; err != nil { return fmt.Errorf("更新 Goods 失败: %w", err) } // 2. 更新 Spec 表 if err := tx.Model(&models.Spec{}). Where("id = ?", specIndex). Update("display_name", req.Name).Error; err != nil { return fmt.Errorf("更新 Spec 失败: %w", err) } // 3. 更新 SpecValue 表 if err := tx.Model(&models.SpecValue{}). Where("spec_id = ?", specIndex). Updates(map[string]interface{}{ "spec_display_name": req.Name, "display_value": req.Name, }).Error; err != nil { return fmt.Errorf("更新 SpecValue 失败: %w", err) } // 3.1 读取最新的 SpecValue var specValues []models.SpecValue if err := tx.Model(&models.SpecValue{}). Where("spec_id = ?", specIndex). Find(&specValues).Error; err != nil { return fmt.Errorf("查询 SpecValue 失败: %w", err) } specValueList := make([]string, 0, len(specValues)) for _, v := range specValues { specValueList = append(specValueList, v.DisplayValue) } specValueListJSON, err := json.Marshal(specValueList) if err != nil { return fmt.Errorf("序列化 SpecValueList 失败: %w", err) } // 4. 更新 GoodsAttribute 表 if err := tx.Model(&models.GoodsAttribute{}). Where("goods_id = ?", goodsId). Updates(map[string]interface{}{ "name": req.Name, "cat_id": req.CatId, "price_original": req.PriceOriginal, "price_rm": req.PriceRm, "main_image": req.Img, "spec_value_list": string(specValueListJSON), }).Error; err != nil { return fmt.Errorf("更新 GoodsAttribute 失败: %w", err) } // 5. 更新 GoodsAttributeCombo 表 if err := tx.Model(&models.GoodsAttributeCombo{}). Where("goods_id = ?", goodsId). Updates(map[string]interface{}{ "name": req.Name, "cat_id": req.CatId, "price_rm": req.PriceRm, "main_image": req.Img, }).Error; err != nil { return fmt.Errorf("更新 GoodsAttributeCombo 失败: %w", err) } return nil } // DeleteMallGoods 根据 goodsId 删除商城商品及相关表数据 func DeleteMallGoods(tx *gorm.DB, goodsId uint32) error { if goodsId == 0 { return errors.New("goodsId 不能为空") } // 1. 查询 Goods,获取 specIndex var goods models.Goods if err := tx.Model(&models.Goods{}). Where("goods_id = ?", goodsId). First(&goods).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("未找到对应的 Goods 记录 goodsId=%d", goodsId) } return fmt.Errorf("查询 Goods 失败: %w", err) } specIndex := goods.SpecIndex // 2. 删除 Goods if err := tx.Where("goods_id = ?", goodsId). Delete(&models.Goods{}).Error; err != nil { return fmt.Errorf("删除 Goods 失败: %w", err) } // 3. 删除 Spec if err := tx.Where("id = ?", specIndex). Delete(&models.Spec{}).Error; err != nil { return fmt.Errorf("删除 Spec 失败: %w", err) } // 4. 删除 SpecValue if err := tx.Where("spec_id = ?", specIndex). Delete(&models.SpecValue{}).Error; err != nil { return fmt.Errorf("删除 SpecValue 失败: %w", err) } // 5. 删除 GoodsAttribute if err := tx.Where("goods_id = ?", goodsId). Delete(&models.GoodsAttribute{}).Error; err != nil { return fmt.Errorf("删除 GoodsAttribute 失败: %w", err) } // 6. 删除 GoodsAttributeCombo if err := tx.Where("goods_id = ?", goodsId). Delete(&models.GoodsAttributeCombo{}).Error; err != nil { return fmt.Errorf("删除 GoodsAttributeCombo 失败: %w", err) } return nil } // CommodityList 商品列表 // @Summary 商品列表 // @Tags 商品资料 // @Produce json // @Accept json // @Param request body models.ErpCommodityListReq true "商品列表模型" // @Success 200 {object} models.ErpCommodityListResp // @Router /api/v1/commodity/list [post] func CommodityList(c *gin.Context) { req := &models.ErpCommodityListReq{} if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") return } resp, err := req.List() if err != nil { //logger.Error("erp commodity list err:", err) app.Error(c, http.StatusInternalServerError, err, "获取失败") return } app.OK(c, resp, "") return } // CommodityDetail 商品详情 // @Summary 商品详情 // @Tags 商品资料 // @Produce json // @Accept json // @Param request body models.CommodityDetailRequest true "商品详情模型" // @Success 200 {object} models.ErpCommodity // @Router /api/v1/commodity/detail [post] func CommodityDetail(c *gin.Context) { var req = new(models.CommodityDetailRequest) if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") return } var commodity models.ErpCommodity qs := orm.Eloquent.Table("erp_commodity") if req.ErpCommodityId != 0 { qs = qs.Where("id=?", req.ErpCommodityId) } if req.SerialNumber != "" { qs = qs.Where("serial_number=?", req.SerialNumber) } err := qs.Find(&commodity).Error if err != nil { //logger.Error("erp commodity err:", err) app.Error(c, http.StatusInternalServerError, err, "获取失败") return } if commodity.IMEIType == 1 { //无串码 commodity.IsIMEI = 2 // 非串码 } else { commodity.IsIMEI = 1 // 串码 } // 查询是否有库存 var count int64 err = orm.Eloquent.Table("erp_stock_commodity").Where("erp_commodity_id = ? and state = ?", commodity.ID, models.InStock).Count(&count).Error if err != nil { app.Error(c, http.StatusInternalServerError, err, "获取商品库存信息失败") return } commodity.StockCount = uint32(count) // 如果有同步小程序商城则查询对应信息 if commodity.IsSyncedToMall == 1 && commodity.GoodsId != 0 { // 查询 Goods var goods models.Goods if err := orm.Eloquent.Model(&models.Goods{}).Where("goods_id = ?", commodity.GoodsId). First(&goods).Error; err != nil { app.Error(c, http.StatusInternalServerError, err, "获取小程序商城信息失败") return } commodity.CatId = goods.CatId commodity.GoodsAccountNum = goods.GoodsAccountNum commodity.PriceOriginal = goods.PriceOriginal / 100 commodity.PriceRm = goods.PriceRm / 100 commodity.Detail = goods.Detail } app.OK(c, commodity, "") return } // CommodityEdit 编辑商品 // @Summary 编辑商品 // @Tags 商品资料 // @Produce json // @Accept json // @Param request body models.CommodityCreateAndEditRequest true "编辑商品模型" // @Success 200 {object} models.ErpCommodity // @Router /api/v1/commodity/edit [post] func CommodityEdit(c *gin.Context) { var req = new(models.CommodityCreateAndEditRequest) err := c.ShouldBindJSON(&req) if err != nil { logger.Error("ShouldBindJSON err:", logger.Field("err", err)) app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误:"+err.Error()) return } // 小程序商品入参校验 if req.IsSyncedToMall == 1 { if req.GoodsAccountNum == 0 { app.Error(c, http.StatusBadRequest, errors.New("goods_account_num empty"), "收款账户不能为空") return } if req.PriceOriginal == 0 { app.Error(c, http.StatusBadRequest, errors.New("price_original empty"), "市场价不能为空") return } else { req.PriceOriginal = req.PriceOriginal * 100 } if req.PriceRm == 0 { req.PriceRm = req.PriceOriginal } else { req.PriceRm = req.PriceRm * 100 } // CatId 必须存在于 GoodsCat 表 var goodsCat models.GoodsCat if err := orm.Eloquent.Model(&models.GoodsCat{}).Where("id = ?", req.CatId).Find(&goodsCat).Error; err != nil { app.Error(c, http.StatusInternalServerError, err, "查询商品分类失败") return } if goodsCat.ID == 0 { app.Error(c, http.StatusBadRequest, errors.New("cat_id invalid"), "商品分类不存在") return } if goodsCat.Pid == 0 { app.Error(c, http.StatusBadRequest, errors.New("cat_id invalid"), "小程序商城分类错误,不能选一级分类") return } } barCode := "" if req.ErpBarcode != "" { // 条码不为空则校验 barCode, err = models.CheckBarcodeById(req.ErpBarcode, req.Id) if err != nil { app.Error(c, http.StatusBadRequest, errors.New("param err"), "参数错误:"+err.Error()) return } } else { // 条码为空则自动生成 barCode, err = models.GenerateBarcode(req.ErpCategoryId) if err != nil { app.Error(c, http.StatusBadRequest, errors.New("gen barcode err"), err.Error()) return } } brokerage1Float := req.Brokerage1 brokerage2Float := req.Brokerage2 memberDiscountFloat := req.MemberDiscount commodity := &models.ErpCommodity{ Name: req.Name, ErpCategoryId: req.ErpCategoryId, ErpCategoryName: "", ErpBarcode: barCode, IMEIType: req.IMEIType, ErpSupplierId: req.ErpSupplierId, ErpSupplierName: "", RetailPrice: req.RetailPrice, MinRetailPrice: req.MinRetailPrice, //StaffCostPrice: req.StaffCostPrice + req.WholesalePrice, StaffCostPrice: req.StaffCostPrice, WholesalePrice: req.WholesalePrice, Brokerage1: brokerage1Float, Brokerage2: brokerage2Float, MemberDiscount: memberDiscountFloat, Origin: req.Origin, Remark: req.Remark, Img: req.Img, StopPurchase: req.StopPurchase, ManufacturerCode: req.ManufacturerCode, IsSyncedToMall: req.IsSyncedToMall, CatId: req.CatId, GoodsAccountNum: req.GoodsAccountNum, PriceOriginal: req.PriceOriginal, PriceRm: req.PriceRm, Detail: req.Detail, } commodity.ID = req.Id err = commodity.SetErpCategory() if err != nil { logger.Error("set erp category err:", logger.Field("err", err)) app.Error(c, http.StatusInternalServerError, err, "操作失败") return } commodity.IdInit() var catCommodity models.ErpCommodity err = orm.Eloquent.Table("erp_commodity").Where("id=?", req.Id).Find(&catCommodity).Error if err != nil { logger.Error("cat erp commodity err:", logger.Field("err", err)) app.Error(c, http.StatusInternalServerError, err, "操作失败") return } commodity.Number = catCommodity.Number commodity.SerialNumber = catCommodity.SerialNumber if commodity.ErpCategory != nil && commodity.ErpCategory.ID != 0 { if commodity.ErpCategory.ID != catCommodity.ErpCategoryId { // 编辑时更换了分类ID commodity.ErpCategoryId = commodity.ErpCategory.ID commodity.ErpCategoryName = commodity.ErpCategory.Name commodity.SerialNumber, err = models.GenerateSerialNumber(commodity.ErpCategoryId) // 更新商品编号 if err != nil { app.Error(c, http.StatusInternalServerError, err, err.Error()) return } } else { commodity.ErpCategory = nil } } if req.IsIMEI == 2 { // 是否串码:1-串码类 2-非串码 commodity.IMEIType = 1 // 系统生成串码:2-是(系统生成) 3-否(手动添加) 1表示非串码 } var sysIsIMEI uint32 switch catCommodity.IMEIType { case 1: sysIsIMEI = 2 // 非串码 case 2: fallthrough case 3: sysIsIMEI = 1 // 串码 } if req.IsIMEI != sysIsIMEI { // 查询是否有库存 var count int64 err = orm.Eloquent.Table("erp_stock_commodity").Where("erp_commodity_id = ? and state = ?", commodity.ID, models.InStock).Count(&count).Error if err != nil { app.Error(c, http.StatusInternalServerError, err, "获取商品库存信息失败") return } if count > 0 { app.Error(c, http.StatusInternalServerError, err, "该商品已有库存,不能修改串码/非串码选项") return } } begin := orm.Eloquent.Begin() if req.IsSyncedToMall == 1 { // 同步到小程序商城 commodity.SaleStatus = catCommodity.SaleStatus commodity.GoodsId = catCommodity.GoodsId if catCommodity.GoodsId != 0 { // 已经同步过,则更新 err = UpdateMallGoodsName(begin, catCommodity.GoodsId, req) if err != nil { begin.Rollback() app.Error(c, http.StatusInternalServerError, err, err.Error()) return } } else { // 未同步过,则新增 goodsId, err := CreateMallGoods(begin, req) if err != nil { begin.Rollback() app.Error(c, http.StatusInternalServerError, err, err.Error()) return } commodity.GoodsId = goodsId //更新 Goods.ErpCommodityId if err = begin.Model(&models.Goods{}). Where("goods_id = ?", goodsId). Update("erp_commodity_id", commodity.ID).Error; err != nil { begin.Rollback() app.Error(c, http.StatusInternalServerError, err, "更新商城商品编号失败") return } } } else { // 未同步到小程序商城 if catCommodity.GoodsId != 0 { // 之前同步了,现在不同步;则删除 err = DeleteMallGoods(begin, catCommodity.GoodsId) if err != nil { begin.Rollback() app.Error(c, http.StatusInternalServerError, err, err.Error()) return } } } err = begin.Omit("created_at").Save(commodity).Error if err != nil { logger.Error("create commodity err:", logger.Field("err", err)) app.Error(c, http.StatusInternalServerError, err, "操作失败") return } // 同步更新库存表和库存商品表的"指导零售价"和"最低零售价"、"分类id"、"分类名称"、"商品编号";库存商品表的"商品条码" err = models.UpdateErpStockAmountInfo(begin, req, barCode, commodity.SerialNumber, commodity.ErpCategory) if err != nil { begin.Rollback() logger.Error("UpdateErpStockAmountInfo err:", logger.Field("err", err)) app.Error(c, http.StatusInternalServerError, err, "操作失败") return } err = begin.Commit().Error if err != nil { logger.Error("commit err:", logger.Field("err", err)) app.Error(c, http.StatusInternalServerError, err, "操作失败") return } app.OK(c, commodity, "") return } // CommodityDel 删除商品 // @Summary 删除商品 // @Tags 商品资料 // @Produce json // @Accept json // @Param request body models.CommodityDelRequest true "删除商品模型" // @Success 200 {object} app.Response // @Router /api/v1/commodity/delete [post] func CommodityDel(c *gin.Context) { var req = new(models.CommodityDelRequest) if err := c.ShouldBindJSON(&req); err != nil { //logger.Error(err) app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") return } // 检查商品是否存在 if !models.IsExistingCommodity(req.ErpCommodityId) { app.Error(c, http.StatusInternalServerError, errors.New("该商品不存在"), "该商品不存在") return } // 删除商品之前需要判断该商品是否有库存 if models.CheckCommodityIsHavaStock(req.ErpCommodityId) { app.Error(c, http.StatusInternalServerError, errors.New("该商品有库存不可删除"), "该商品有库存不可删除") return } begin := orm.Eloquent.Begin() // 查询商品 var waitDeleteCommodity models.ErpCommodity err := begin.Model(&models.ErpCommodity{}).Where("id = ?", req.ErpCommodityId).First(&waitDeleteCommodity).Error if err != nil { app.Error(c, http.StatusInternalServerError, err, "未找到对应的商品") return } // 同步删除小程序商城商品 if waitDeleteCommodity.GoodsId != 0 { // 之前同步了,现在不同步;则删除 err = DeleteMallGoods(begin, waitDeleteCommodity.GoodsId) if err != nil { begin.Rollback() app.Error(c, http.StatusInternalServerError, err, err.Error()) return } } // 删除商品资料 err = begin.Table("erp_commodity").Where("id=?", req.ErpCommodityId).Delete(&models.ErpCommodity{}).Error if err != nil { begin.Rollback() app.Error(c, http.StatusInternalServerError, err, "获取失败") return } err = begin.Commit().Error if err != nil { logger.Error("delete err:", logger.Field("err", err)) app.Error(c, http.StatusInternalServerError, err, "操作失败") return } app.OK(c, nil, "删除成功") return } // CommodityImportView 导入商品资料预览 // @Summary 导入商品资料预览 // @Tags 商品资料 // @Produce json // @Accept json // @Param file body string true "上传excel文件" // @Success 200 {array} models.CommodityExcel // @Router /api/v1/commodity/import_commodity_view [post] func CommodityImportView(c *gin.Context) { file, header, err := c.Request.FormFile("file") if err != nil { //logger.Error("form file err:", err) app.Error(c, http.StatusInternalServerError, err, "预览失败") return } readAll, err := io.ReadAll(file) if err != nil { //logger.Error("read all err:", err) app.Error(c, http.StatusInternalServerError, err, "预览失败") return } fmt.Println("header:", header.Filename) _, colsMap, err := models.FileExcelReader([]byte(readAll), nil) if err != nil { //logger.Error("file excel reader err:", err) app.Error(c, http.StatusInternalServerError, err, "预览失败") return } fmt.Println("colsMap:", colsMap) if len(colsMap) != 0 { colsMap = colsMap[1:] } app.OK(c, &colsMap, "") return } // CommodityImport 导入商品资料 // @Summary 导入商品资料 // @Tags 商品资料 // @Produce json // @Accept json // @Param file body string true "上传excel文件" // @Success 200 {object} app.Response // @Router /api/v1/commodity/import_commodity [post] func CommodityImport(c *gin.Context) { file, header, err := c.Request.FormFile("file") if err != nil { //logger.Error("form file err:", err) app.Error(c, http.StatusInternalServerError, err, "预览失败") return } readAll, err := io.ReadAll(file) if err != nil { //logger.Error("read all err:", err) app.Error(c, http.StatusInternalServerError, err, "预览失败") return } fmt.Println("header:", header.Filename) _, colsMap, err := models.FileExcelImport(c, []byte(readAll), nil, 2, 0) if err != nil { //logger.Error("file excel reader err:", err) app.Error(c, http.StatusInternalServerError, err, err.Error()) return } fmt.Println("colsMap:", colsMap) if len(colsMap) != 0 { colsMap = colsMap[1:] } err = models.ImportCommodityData(colsMap, middleware.GetCooperativeBusinessId(c)) if err != nil { //logger.Error("file excel reader err:", err) app.Error(c, http.StatusInternalServerError, err, err.Error()) return } app.OK(c, nil, "导入成功") return } // CommodityBatchSaleStatus 批量上架/下架商品 // @Summary 批量上架/下架商品 // @Tags 商品资料 // @Produce json // @Accept json // @Param request body models.CommodityBatchSaleStatusRequest true "批量上架/下架模型" // @Success 200 {object} app.Response // @Router /api/v1/commodity/batch_sale_status [post] func CommodityBatchSaleStatus(c *gin.Context) { var req = new(models.CommodityBatchSaleStatusRequest) if err := c.ShouldBindJSON(&req); err != nil { app.Error(c, http.StatusBadRequest, errors.New("para err"), "参数错误") return } if len(req.ErpCommodityIds) == 0 { app.Error(c, http.StatusBadRequest, errors.New("empty ids"), "未选择任何商品") return } if req.SaleStatus != 1 && req.SaleStatus != 2 { app.Error(c, http.StatusBadRequest, errors.New("invalid status"), "sale_status 必须为 1 或 2") return } // 1. 查询 ERP 商品(无需事务) var commodities []models.ErpCommodity if err := orm.Eloquent.Model(&models.ErpCommodity{}). Select("id, name, goods_id, is_synced_to_mall, sale_status"). Where("id IN (?)", req.ErpCommodityIds). Find(&commodities).Error; err != nil { app.Error(c, http.StatusInternalServerError, err, "查询商品失败") return } if len(commodities) == 0 { app.Error(c, http.StatusBadRequest, errors.New("not found"), "未找到对应商品") return } var updateCommodityIDs []uint32 var updateGoodsIDs []uint32 for _, item := range commodities { // 判断是否同步到小程序商城 if item.IsSyncedToMall != 1 || item.GoodsId == 0 { app.Error(c, http.StatusBadRequest, fmt.Errorf("id=%d not synced", item.ID), fmt.Sprintf("商品[%s],id=%d 未同步至商城,无法操作", item.Name, item.ID)) return } // 已经是目标状态的商品跳过 if item.SaleStatus == uint32(req.SaleStatus) { continue } updateCommodityIDs = append(updateCommodityIDs, item.ID) updateGoodsIDs = append(updateGoodsIDs, item.GoodsId) } if len(updateCommodityIDs) == 0 { app.OK(c, nil, "没有需要更新的商品") return } // 2. 开启事务进行分批更新 tx := orm.Eloquent.Begin() if tx.Error != nil { app.Error(c, http.StatusInternalServerError, tx.Error, "数据库错误") return } batchSize := 200 // 分批更新 ERP 商品 sale_status for i := 0; i < len(updateCommodityIDs); i += batchSize { end := i + batchSize if end > len(updateCommodityIDs) { end = len(updateCommodityIDs) } if err := tx.Model(&models.ErpCommodity{}). Where("id IN (?)", updateCommodityIDs[i:end]). Update("sale_status", req.SaleStatus).Error; err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, "更新 ERP 商品失败") return } } // 分批更新 Goods sale_status for i := 0; i < len(updateGoodsIDs); i += batchSize { end := i + batchSize if end > len(updateGoodsIDs) { end = len(updateGoodsIDs) } if err := tx.Model(&models.Goods{}). Where("goods_id IN (?)", updateGoodsIDs[i:end]). Update("sale_status", req.SaleStatus).Error; err != nil { tx.Rollback() app.Error(c, http.StatusInternalServerError, err, "更新 Goods 表失败") return } } if err := tx.Commit().Error; err != nil { app.Error(c, http.StatusInternalServerError, err, "提交失败") return } app.OK(c, nil, "操作成功") }