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