mh_goadmin_server/app/admin/apis/basic/commodity.go

1101 lines
32 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 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, "操作成功")
}