From fafac0557001ee6f315b9c8c1dddcb753eadba97 Mon Sep 17 00:00:00 2001 From: Administrator <1269936630@qq.com> Date: Tue, 16 Jun 2026 16:11:37 +0800 Subject: [PATCH] =?UTF-8?q?1.=E8=BF=94=E5=9B=9Euserlist=202.=E9=80=9A?= =?UTF-8?q?=E8=BF=87use=5Fid,product=5Fid=20=E8=BF=94=E5=9B=9E=E5=85=B3?= =?UTF-8?q?=E8=81=94=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controllers/employee.go | 25 +++ models/employee.go | 13 ++ models/request/Employee.go | 25 ++- models/request/product.go | 1 + models/response/Employee.go | 60 ++++++- models/response/product.go | 14 +- routes/routes.go | 17 +- service/employee.go | 148 ++++++++++++++++- service/process.go | 45 ++++-- service/product.go | 314 +++++++++++++++++++++++++++++++++++- 10 files changed, 622 insertions(+), 40 deletions(-) diff --git a/controllers/employee.go b/controllers/employee.go index 5e8f325..e4239c4 100644 --- a/controllers/employee.go +++ b/controllers/employee.go @@ -327,3 +327,28 @@ func (r *EmployeeApi) GetUserList(c *gin.Context) { "data": result, }) } + +// UpdateEmployeeSplitAccountConfigByAboutId 根据about_id更新员工分账配置ID +func (r *EmployeeApi) UpdateEmployeeSplitAccountConfigByAboutId(c *gin.Context) { + var req systemReq.UpdateEmployeeSplitAccountConfigRequest + + if err := c.ShouldBind(&req); err != nil { + ValidAndFail(constant.LoggerChannelRequest, "更新员工分账配置请求参数异常", "参数错误: "+err.Error(), c, err) + return + } + + // 获取当前用户信息 + userInfo := utils.GetUserInfo(c) + + result, err := employeeService.UpdateEmployeeSplitAccountConfigByAboutId(req, userInfo.Username) + if err != nil { + utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{ + "source": "更新员工分账配置异常", + "err_msg": err.Error(), + }) + systemRes.FailWithMessage(err.Error(), c) + return + } + + systemRes.OkWithDetailed(result, "更新成功", c) +} diff --git a/models/employee.go b/models/employee.go index 7afd27d..1ae1670 100644 --- a/models/employee.go +++ b/models/employee.go @@ -25,6 +25,19 @@ type Employee struct { ExpireTime int64 `json:"expire_time" gorm:"not null;default:0;comment:过期时间"` } +// UpdateEmployeeSplitAccountConfigResponse 更新员工分账配置响应 +type UpdateEmployeeSplitAccountConfigResponse struct { + ID int64 `json:"id"` + EmployeeIDStr string `json:"employee_id_str"` + Username string `json:"username"` + Name string `json:"name"` + Phone string `json:"phone"` + AboutId int64 `json:"about_id"` + SplitAccountConfigId int64 `json:"split_account_config_id"` + Status int8 `json:"status"` + UpdatedAt int64 `json:"updated_at"` +} + func (Employee) TableName() string { return "employees" } diff --git a/models/request/Employee.go b/models/request/Employee.go index daa6607..a71cc7a 100644 --- a/models/request/Employee.go +++ b/models/request/Employee.go @@ -65,8 +65,25 @@ type SetEmployeeLevelRequest struct { // GetUserListRequest 获取用户列表请求 type GetUserListRequest struct { - Page int `form:"page"` - PageSize int `form:"page_size"` - Keyword string `form:"keyword"` - Status string `form:"status"` + Page int `form:"page"` // 页码 + PageSize int `form:"page_size"` // 页大小 + Username string `form:"username"` // 用户名 + Tel string `form:"tel"` // 手机号 + //Keyword string `form:"keyword"` // 关键词 注释将keyword改为 username 和 tel + Status string `form:"status"` // 状态 +} + +// GetUserListRequest 获取用户列表请求 +/*type GetUserListRequest struct { + Page int `form:"page"` // 页码 + PageSize int `form:"page_size"` // 页大小 + Username string `form:"username"` // 用户名 + Tel string `form:"tel"` // 手机号 + Status string `form:"status"` // 状态 +}*/ + +// UpdateEmployeeSplitAccountConfigRequest 根据about_id更新员工分账配置请求 +type UpdateEmployeeSplitAccountConfigRequest struct { + AboutId int64 `form:"about_id" binding:"required"` + SplitAccountConfigId int64 `form:"split_account_config_id" binding:"required"` } diff --git a/models/request/product.go b/models/request/product.go index 71959bf..d951adc 100644 --- a/models/request/product.go +++ b/models/request/product.go @@ -154,6 +154,7 @@ type DeleteProductLogRequest struct { type GetProductInventoryRequest struct { UserID int64 `form:"user_id" binding:"required"` // 用户ID ProductID int64 `form:"product_id" binding:"required"` // 商品ID + Type int8 `form:"type"` // 类型: 0=原始数据, 1=按商品名称+ISBN+仓库聚合 } type GetShopProductDetailRequest struct { diff --git a/models/response/Employee.go b/models/response/Employee.go index 1c2e1c1..b294987 100644 --- a/models/response/Employee.go +++ b/models/response/Employee.go @@ -1,6 +1,7 @@ package response import ( + "encoding/json" "psi/models" ) @@ -102,13 +103,14 @@ type EmployeeLevelConfigResponse struct { // UserListItem 用户列表项 type UserListItem struct { - ID int64 `json:"id"` - EmployeeIDStr string `json:"employee_id_str"` - Username string `json:"username"` - Name string `json:"name"` - Role int64 `json:"role"` - Status int8 `json:"status"` - SplitAccountConfigId int64 `json:"split_account_config_id"` + ID int64 `json:"id"` + EmployeeIDStr string `json:"employee_id_str"` + Username string `json:"username"` + Name string `json:"name"` + Role int64 `json:"role"` + Status int8 `json:"status"` + SplitAccountConfigId int64 `json:"split_account_config_id"` + SplitAccountConfig *SplitAccountConfigInfo `json:"split_account_config,omitempty"` // 分账配置详情 } // GetUserListResponse 用户列表响应 @@ -118,3 +120,47 @@ type GetUserListResponse struct { Page int `json:"page"` PageSize int `json:"page_size"` } + +// UpdateEmployeeSplitAccountConfigResponse 更新员工分账配置响应 +type UpdateEmployeeSplitAccountConfigResponse struct { + ID int64 `json:"id"` + EmployeeIDStr string `json:"employee_id_str"` + Username string `json:"username"` + Name string `json:"name"` + Phone string `json:"phone"` + AboutId int64 `json:"about_id"` + SplitAccountConfigId int64 `json:"split_account_config_id"` + Status int8 `json:"status"` + UpdatedAt int64 `json:"updated_at"` +} + +// CurrentUserResponse 当前用户信息响应(包含分账配置) +type CurrentUserResponse struct { + ID int64 `json:"id"` + EmployeeIDStr string `json:"employee_id_str"` + Username string `json:"username"` + Name string `json:"name"` + Phone string `json:"phone"` + Role int64 `json:"role"` + Fid int64 `json:"fid"` + AboutId int64 `json:"about_id"` + Status int8 `json:"status"` + Score int64 `json:"score"` + SplitAccountConfigId int64 `json:"split_account_config_id"` + SplitAccountConfig *SplitAccountConfigInfo `json:"split_account_config,omitempty"` // 分账配置详情 + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +// SplitAccountConfigInfo 分账配置信息 +type SplitAccountConfigInfo struct { + ID int64 `json:"id"` + RuleName string `json:"rule_name"` + RuleValue json.RawMessage `json:"rule_value"` + Status int8 `json:"status"` + Description string `json:"description"` + CreatedBy string `json:"created_by"` + UpdatedBy string `json:"updated_by"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} diff --git a/models/response/product.go b/models/response/product.go index f9e74ed..0de73d7 100644 --- a/models/response/product.go +++ b/models/response/product.go @@ -146,7 +146,8 @@ type ProductLogListResponse struct { // ProductInventoryResponse 商品库存响应 type ProductInventoryResponse struct { - Quantity int64 `json:"quantity"` + Quantity int64 `json:"quantity"` // 总库存数量(type=0时使用) + Warehouses []ProductInventoryWarehouse `json:"warehouses,omitempty"` // 仓库列表(type=1时使用) } // ShopProductDetailResponse 店铺商品详情响应 @@ -199,3 +200,14 @@ type ProductInShop struct { CreatedAt int64 `json:"created_at"` // 创建时间 UpdatedAt int64 `json:"updated_at"` // 更新时间 } + +// ProductInventoryWarehouse 商品库存仓库信息 +type ProductInventoryWarehouse struct { + WarehouseID int64 `json:"warehouse_id"` // 仓库ID + WarehouseName string `json:"warehouse_name"` // 仓库名称 + WarehouseCode string `json:"warehouse_code"` // 仓库编码 + ProductName string `json:"product_name"` // 商品名称 + ISBN string `json:"isbn"` // ISBN/条码 + Appearance int64 `json:"appearance"` // 品相 + TotalQuantity int64 `json:"total_quantity"` // 该仓库下该商品的总库存 +} diff --git a/routes/routes.go b/routes/routes.go index 10495ab..c688624 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -98,7 +98,7 @@ func initRouter() (r *gin.Engine) { // 管理员 auth.GET("/user/current", employeeApi.GetCurrentUser) // 获取当前用户信息 auth.POST("/logout", employeeApi.Logout) // 登出 - auth.POST("/userList", employeeApi.GetUserList) // 获取用户列表 + auth.GET("/userList", employeeApi.GetUserList) // 获取用户列表 // 配置管理 auth.GET("/config/list", configApi.GetConfigList) // 获取配置列表 auth.GET("/config/detail/:id", configApi.GetConfigDetail) // 获取配置详情 @@ -254,13 +254,14 @@ func initRouter() (r *gin.Engine) { admin.Use(middleware.AdminRequired()) { // 员工管理 - admin.GET("/employee/list", employeeApi.GetEmployeeList) // 获取员工列表 - admin.POST("/employee/add", employeeApi.AddEmployee) // 添加员工 - admin.POST("/employee/update", employeeApi.UpdatePasswordEmployee) // 更新员工密码 - admin.POST("/employee/update_expire_time", employeeApi.UpdateExpireTimeEmployee) // 更新员工到期时间 - admin.GET("/employee/check_expire_time", employeeApi.CheckExpireTimeEmployee) // 检查员工到期时间 - admin.POST("/employee/set_level", employeeApi.SetEmployeeLevel) // 设置员工等级 - admin.GET("/employee/level_config", employeeApi.GetLevelConfigList) // 获取员工等级配置列表 + admin.GET("/employee/list", employeeApi.GetEmployeeList) // 获取员工列表 + admin.POST("/employee/add", employeeApi.AddEmployee) // 添加员工 + admin.POST("/employee/update", employeeApi.UpdatePasswordEmployee) // 更新员工密码 + admin.POST("/employee/update_expire_time", employeeApi.UpdateExpireTimeEmployee) // 更新员工到期时间 + admin.GET("/employee/check_expire_time", employeeApi.CheckExpireTimeEmployee) // 检查员工到期时间 + admin.POST("/employee/set_level", employeeApi.SetEmployeeLevel) // 设置员工等级 + admin.GET("/employee/level_config", employeeApi.GetLevelConfigList) // 获取员工等级配置列表 + admin.POST("/employee/update-split-account-config", employeeApi.UpdateEmployeeSplitAccountConfigByAboutId) // 根据about_id更新员工分账配置ID // 用户类型管理 admin.GET("/user-type/list", userTypeApi.GetUserTypeList) // 获取用户类型列表 admin.GET("/user-type/detail/:id", userTypeApi.GetUserTypeDetail) // 获取用户类型详情 diff --git a/service/employee.go b/service/employee.go index fa1d005..1885a9f 100644 --- a/service/employee.go +++ b/service/employee.go @@ -839,7 +839,7 @@ func (s *EmployeeService) createEmployeeLevelLog(empId int64, operationType int8 } // GetUserList 获取用户列表(主库) -func (s *EmployeeService) GetUserList(req systemReq.GetUserListRequest) (*systemRes.GetUserListResponse, error) { +/*func (s *EmployeeService) GetUserList(req systemReq.GetUserListRequest) (*systemRes.GetUserListResponse, error) { if req.Page < 1 { req.Page = 1 } @@ -888,3 +888,149 @@ func (s *EmployeeService) GetUserList(req systemReq.GetUserListRequest) (*system PageSize: req.PageSize, }, nil } +*/ +func (s *EmployeeService) GetUserList(req systemReq.GetUserListRequest) (*systemRes.GetUserListResponse, error) { + if req.Page < 1 { + req.Page = 1 + } + if req.PageSize < 1 || req.PageSize > 100 { + req.PageSize = 20 + } + + query := database.DB.Model(&models.Employee{}).Where("deleted_at = ?", 0) + + if req.Status != "" { + query = query.Where("status = ?", req.Status) + } + if req.Username != "" { + query = query.Where("username LIKE ?", "%"+req.Username+"%") + } + if req.Tel != "" { + query = query.Where("phone LIKE ?", "%"+req.Tel+"%") + } + + var total int64 + if err := query.Count(&total).Error; err != nil { + return nil, utils.NewError("查询总数失败") + } + + var employees []models.Employee + offset := (req.Page - 1) * req.PageSize + if err := query.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&employees).Error; err != nil { + return nil, utils.NewError("查询用户列表失败") + } + + // 收集所有需要查询的分账配置ID + splitConfigIDs := make([]int64, 0) + for _, emp := range employees { + if emp.SplitAccountConfigId > 0 { + splitConfigIDs = append(splitConfigIDs, emp.SplitAccountConfigId) + } + } + + // 批量查询分账配置 + splitConfigMap := make(map[int64]*systemRes.SplitAccountConfigInfo) + if len(splitConfigIDs) > 0 { + var splitConfigs []models.SplitAccountConfig + database.DB.Where("id IN ? AND deleted_at = ?", splitConfigIDs, 0).Find(&splitConfigs) + + for _, config := range splitConfigs { + splitConfigMap[config.ID] = &systemRes.SplitAccountConfigInfo{ + ID: config.ID, + RuleName: config.RuleName, + RuleValue: json.RawMessage(config.RuleValue), + Status: config.Status, + Description: config.Description, + CreatedBy: config.CreatedBy, + UpdatedBy: config.UpdatedBy, + CreatedAt: config.CreatedAt, + UpdatedAt: config.UpdatedAt, + } + } + } + + var items []systemRes.UserListItem + for _, emp := range employees { + item := systemRes.UserListItem{ + ID: emp.ID, + EmployeeIDStr: emp.EmployeeIDStr, + Username: emp.Username, + Name: emp.Name, + Role: emp.Role, + Status: emp.Status, + SplitAccountConfigId: emp.SplitAccountConfigId, + } + + // 如果有关联的分账配置,添加详情 + if emp.SplitAccountConfigId > 0 { + if config, exists := splitConfigMap[emp.SplitAccountConfigId]; exists { + item.SplitAccountConfig = config + } + } + + items = append(items, item) + } + + return &systemRes.GetUserListResponse{ + List: items, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + }, nil +} + +// UpdateEmployeeSplitAccountConfigByAboutId 根据about_id更新员工分账配置ID +func (s *EmployeeService) UpdateEmployeeSplitAccountConfigByAboutId(req systemReq.UpdateEmployeeSplitAccountConfigRequest, username string) (*systemRes.UpdateEmployeeSplitAccountConfigResponse, error) { + var employee models.Employee + result := database.DB.Where("about_id = ? AND deleted_at = ?", req.AboutId, 0).First(&employee) + if result.Error != nil { + return nil, utils.NewError("员工不存在") + } + + updateData := map[string]interface{}{ + "split_account_config_id": req.SplitAccountConfigId, + "updated_at": time.Now().Unix(), + } + + if err := database.DB.Model(&employee).Updates(updateData).Error; err != nil { + return nil, utils.NewError("更新员工分账配置失败: " + err.Error()) + } + + // 重新查询更新后的员工数据 + if err := database.DB.Where("id = ? AND deleted_at = ?", employee.ID, 0).First(&employee).Error; err != nil { + return nil, utils.NewError("查询更新后的员工数据失败") + } + + return &systemRes.UpdateEmployeeSplitAccountConfigResponse{ + ID: employee.ID, + EmployeeIDStr: employee.EmployeeIDStr, + Username: employee.Username, + Name: employee.Name, + Phone: employee.Phone, + AboutId: employee.AboutId, + SplitAccountConfigId: employee.SplitAccountConfigId, + Status: employee.Status, + UpdatedAt: employee.UpdatedAt, + }, nil +} + +// ... existing code ... +/*func (s *EmployeeService) UpdateEmployeeSplitAccountConfigByAboutId(req systemReq.UpdateEmployeeSplitAccountConfigRequest, username string) error { + var employee models.Employee + result := database.DB.Where("about_id = ? AND deleted_at = ?", req.AboutId, 0).First(&employee) + if result.Error != nil { + return utils.NewError("员工不存在") + } + + updateData := map[string]interface{}{ + "split_account_config_id": req.SplitAccountConfigId, + "updated_at": time.Now().Unix(), + } + + if err := database.DB.Model(&employee).Updates(updateData).Error; err != nil { + return utils.NewError("更新员工分账配置失败: " + err.Error()) + } + + return nil +} +*/ diff --git a/service/process.go b/service/process.go index 5d11658..8c33444 100644 --- a/service/process.go +++ b/service/process.go @@ -3974,12 +3974,7 @@ func (s *ProcessService) syncProductsToExternal(receivingOrderID, waveTaskID, us group.totalQty += item.quantity } else { // 新建ISBN组 - var imgList []string - if product.LiveImage != nil && len(product.LiveImage) > 0 { - if err := json.Unmarshal(product.LiveImage, &imgList); err != nil { - return fmt.Errorf("解析json失败: %v", err) - } - } + imgList := parseImageList(product.LiveImage) skuCode := warehouse.Code if item.locationID > 0 { @@ -4046,12 +4041,7 @@ func (s *ProcessService) syncProductsToExternal(receivingOrderID, waveTaskID, us } isbn := product.Barcode - var imgList []string - if product.LiveImage != nil && len(product.LiveImage) > 0 { - if err := json.Unmarshal(product.LiveImage, &imgList); err != nil { - return fmt.Errorf("解析json失败: %v", err) - } - } + imgList := parseImageList(product.LiveImage) skuCode := warehouse.Code if item.locationID > 0 { @@ -4379,3 +4369,34 @@ func getSalesStatusText(status int8) string { return "未知状态" } } + +// parseImageList 解析图片列表,支持JSON数组和逗号分隔字符串两种格式 +func parseImageList(liveImage datatypes.JSON) []string { + if liveImage == nil || len(liveImage) == 0 { + return []string{} + } + + // 尝试解析为字符串数组 + var imgList []string + if err := json.Unmarshal(liveImage, &imgList); err == nil { + return imgList + } + + // 如果解析失败,尝试解析为字符串,然后按逗号分割 + var imgStr string + if err := json.Unmarshal(liveImage, &imgStr); err == nil { + // 去除空格并按逗号分割 + parts := strings.Split(imgStr, ",") + result := make([]string, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + result = append(result, trimmed) + } + } + return result + } + + // 都不成功,返回空数组 + return []string{} +} diff --git a/service/product.go b/service/product.go index fbbf499..1db3d14 100644 --- a/service/product.go +++ b/service/product.go @@ -651,18 +651,123 @@ func (s *ProductService) UpdatePrice(req systemReq.UpdatePriceRequest) error { return nil } -// GetProductInventory 获取商品库存数量 +//// GetProductInventory 获取商品库存数量 +//func (s *ProductService) GetProductInventory(req systemReq.GetProductInventoryRequest) (*systemRes.ProductInventoryResponse, error) { +// databaseConn, err := database.GetTenantDB(req.UserID) +// if err != nil { +// return nil, fmt.Errorf("获取数据库连接失败: %v", err) +// } +// +// // 验证商品是否存在 +// var product models.Product +// if err := databaseConn.Where("id = ? AND is_del = ?", req.ProductID, 0).First(&product).Error; err != nil { +// return nil, fmt.Errorf("商品不存在") +// } +// +// //// type=1: 按品相+ISBN+仓库聚合 +// //if req.Type == 1 { +// // type warehouseStock struct { +// // WarehouseID int64 `gorm:"column:warehouse_id"` +// // WarehouseName string `gorm:"column:warehouse_name"` +// // WarehouseCode string `gorm:"column:warehouse_code"` +// // ProductName string `gorm:"column:product_name"` +// // ISBN string `gorm:"column:isbn"` +// // Appearance int64 `gorm:"column:appearance"` +// // TotalQuantity int64 `gorm:"column:total_quantity"` +// // } +// // +// // var warehouseList []warehouseStock +// // databaseConn.Table("inventory"). +// // Select(` +// // inventory.warehouse_id, +// // w.name as warehouse_name, +// // w.code as warehouse_code, +// // p.name as product_name, +// // p.barcode as isbn, +// // p.appearance as appearance, +// // COALESCE(SUM(inventory.quantity), 0) as total_quantity +// // `). +// // Joins("LEFT JOIN warehouse w ON inventory.warehouse_id = w.id AND w.is_del = ?", 0). +// // Joins("LEFT JOIN product p ON inventory.product_id = p.id AND p.is_del = ?", 0). +// // Where("inventory.product_id = ? AND inventory.is_del = ?", req.ProductID, 0). +// // Group("inventory.warehouse_id, w.name, w.code, p.name, p.barcode, p.appearance"). +// // Scan(&warehouseList) +// // +// // warehouses := make([]systemRes.ProductInventoryWarehouse, 0, len(warehouseList)) +// // totalQuantity := int64(0) +// // for _, ws := range warehouseList { +// // warehouses = append(warehouses, systemRes.ProductInventoryWarehouse{ +// // WarehouseID: ws.WarehouseID, +// // WarehouseName: ws.WarehouseName, +// // WarehouseCode: ws.WarehouseCode, +// // ProductName: ws.ProductName, +// // ISBN: ws.ISBN, +// // Appearance: ws.Appearance, +// // TotalQuantity: ws.TotalQuantity, +// // }) +// // totalQuantity += ws.TotalQuantity +// // } +// +// return &systemRes.ProductInventoryResponse{ +// Quantity: totalQuantity, +// //Warehouses: warehouses, +// }, nil +// } +// +// // type=0: 返回原始数据(总库存) +// var totalQuantity int64 +// //databaseConn.Table("inventory"). +// // Select("COALESCE(SUM(quantity), 0)"). +// // Where("product_id = ? AND is_del = ?", req.ProductID, 0). +// // Scan(&totalQuantity) +// +// return &systemRes.ProductInventoryResponse{ +// Quantity: totalQuantity, +// }, nil +//} + func (s *ProductService) GetProductInventory(req systemReq.GetProductInventoryRequest) (*systemRes.ProductInventoryResponse, error) { databaseConn, err := database.GetTenantDB(req.UserID) if err != nil { return nil, fmt.Errorf("获取数据库连接失败: %v", err) } + // 验证商品是否存在,并获取商品信息 + var product models.Product + if err := databaseConn.Where("id = ? AND is_del = ?", req.ProductID, 0).First(&product).Error; err != nil { + return nil, fmt.Errorf("商品不存在") + } + var totalQuantity int64 - databaseConn.Table("inventory"). - Select("COALESCE(SUM(quantity), 0)"). - Where("product_id = ? AND is_del = ?", req.ProductID, 0). - Scan(&totalQuantity) + + // type=1: 按品相+ISBN+仓库分组后统计总数量 + if req.Type == 1 { + type GroupStock struct { + TotalQuantity int64 `gorm:"column:total_quantity"` + } + + var groupList []GroupStock + // 先根据商品的 ISBN 和品相,查询所有匹配的库存记录,再按仓库分组统计 + databaseConn.Table("inventory"). + Select(` + COALESCE(SUM(inventory.quantity), 0) as total_quantity + `). + Joins("LEFT JOIN product p ON inventory.product_id = p.id AND p.is_del = ?", 0). + Where("p.barcode = ? AND p.appearance = ? AND inventory.warehouse_id IS NOT NULL AND inventory.is_del = ?", + product.Barcode, product.Appearance, 0). + Group("inventory.warehouse_id"). + Scan(&groupList) + + // 累加所有分组的数量 + for _, group := range groupList { + totalQuantity += group.TotalQuantity + } + } else { + databaseConn.Table("inventory"). + Select("COALESCE(SUM(quantity), 0)"). + Where("product_id = ? AND is_del = ?", req.ProductID, 0). + Scan(&totalQuantity) + } return &systemRes.ProductInventoryResponse{ Quantity: totalQuantity, @@ -1443,7 +1548,8 @@ func (s *ProductService) PushProductToShop(req systemReq.PushProductToShopReques }, nil } -func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTask, product models.Product, stock, salePrice, cost, now, userID int64, warehouseCode string, locationCode string, condition int64) { +// 批量上架 +/*func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTask, product models.Product, stock, salePrice, cost, now, userID int64, warehouseCode string, locationCode string, condition int64) { isbn := product.Barcode var imgList []string if product.LiveImage != nil && len(product.LiveImage) > 0 { @@ -1527,6 +1633,89 @@ func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTas }) } } +*/ + +// batchPushProductBody 批量上架商品到外部任务 +func (s *ProductService) batchPushProductBody(db *gorm.DB, outTask models.OutTask, product models.Product, stock, salePrice, cost, now, userID int64, warehouseCode string, locationCode string, condition int64) { + isbn := product.Barcode + + // 使用 parseImageList 解析图片列表,支持多种格式 + imgList := s.parseImageList(product.LiveImage) + + msgData := map[string]interface{}{ + "product_id": product.ID, + "user_id": strconv.FormatInt(userID, 10), + "is_distribution": "1", + } + msgJSON, _ := json.Marshal(msgData) + + bodyData := map[string]interface{}{ + "book_info": map[string]interface{}{ + "isbn": isbn, + "image_object": map[string]interface{}{ + "carousel_url_array": imgList, + }, + }, + "detail": map[string]interface{}{ + "stock": stock, + "price": salePrice, + "shipping_cost": cost, + "msg": string(msgJSON), + "sku_code": warehouseCode + "##" + locationCode, + "condition": condition, + }, + } + + bodyDataJSON, err := json.Marshal(bodyData) + if err != nil { + s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("序列化请求体失败: %v", err), db) + return + } + + bodyList := []string{string(bodyDataJSON)} + taskID := fmt.Sprintf("%d", outTask.OutTaskID) + allBody := strings.Join(bodyList, "") + + signParams := map[string]string{ + "task_id": taskID, + "body": allBody, + } + + sign := utils.SignParams(signParams) + + url := config.AppConfig.ExternalAPI.SyncTaskBodyURL + resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign) + if err != nil { + s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("请求外部接口失败: %v", err), db) + return + } + + var resData systemRes.ExternalAPIResponse + if err := json.Unmarshal([]byte(resp), &resData); err != nil { + s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("解析响应失败: %v", err), db) + return + } + + if resData.Code != "200" { + s.saveOutTaskLogWithStock(outTask, product.ID, isbn, product.LiveImage, stock, salePrice, cost, fmt.Sprintf("外部接口返回错误: code=%s, msg=%s", resData.Code, resData.Msg), db) + return + } + + if updateErr := db.Model(&models.OutTaskLog{}). + Where("shop_id = ? AND product_id = ? AND is_del = ?", outTask.ShopID, product.ID, 0). + Updates(map[string]interface{}{ + "status": 1, + "msg": "成功", + "updated_at": now, + }).Error; updateErr != nil { + utils.ErrorLog(constant.LoggerChannelWork, map[string]interface{}{ + "source": "批量推送-更新历史日志状态失败", + "shop_id": outTask.ShopID, + "product_id": product.ID, + "error": fmt.Sprintf("更新失败: %v", updateErr), + }) + } +} // saveRetryLog 保存重试任务日志 func (s *ProductService) saveRetryLog(shopID, outTaskID, productID int64, product models.Product, waveTaskDetail models.WaveTaskDetail, hasWaveTask bool, msg string, db *gorm.DB) { @@ -1765,7 +1954,7 @@ func (s *ProductService) syncPriceToExternal(waveTaskDetail models.WaveTaskDetai } // syncPriceToExternalTask 同步售价到单个外部任务 -func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product models.Product, waveTaskDetail models.WaveTaskDetail, salePrice, cost, userId int64, db *gorm.DB) error { +/*func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product models.Product, waveTaskDetail models.WaveTaskDetail, salePrice, cost, userId int64, db *gorm.DB) error { isbn := product.Barcode var imgList []string if product.LiveImage != nil && len(product.LiveImage) > 0 { @@ -1837,6 +2026,78 @@ func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product return nil } +*/ + +// syncPriceToExternalTask 同步售价到单个外部任务 +func (s *ProductService) syncPriceToExternalTask(outTask models.OutTask, product models.Product, waveTaskDetail models.WaveTaskDetail, salePrice, cost, userId int64, db *gorm.DB) error { + isbn := product.Barcode + + // 使用 parseImageList 解析图片列表,支持多种格式 + imgList := s.parseImageList(product.LiveImage) + + msgData := map[string]interface{}{ + "product_id": product.ID, + "user_id": fmt.Sprintf("%d", userId), + } + msgJSON, _ := json.Marshal(msgData) + + bodyData := map[string]interface{}{ + "book_info": map[string]interface{}{ + "isbn": isbn, + "image_object": map[string]interface{}{ + "carousel_url_array": imgList, + }, + }, + "detail": map[string]interface{}{ + "stock": waveTaskDetail.PlannedQuantity, + "price": salePrice, + "shipping_cost": cost, + "msg": string(msgJSON), + }, + } + + bodyDataJSON, err := json.Marshal(bodyData) + if err != nil { + return fmt.Errorf("序列化请求体失败: %v", err) + } + + bodyList := []string{ + string(bodyDataJSON), + } + + taskID := fmt.Sprintf("%d", outTask.OutTaskID) + + allBody := strings.Join(bodyList, "") // 直接无缝拼接(和服务端一致) + + signParams := map[string]string{ + "task_id": taskID, + "body": allBody, + } + + sign := utils.SignParams(signParams) + + // 发送请求 + url := config.AppConfig.ExternalAPI.SyncTaskBodyURL + resp, err := utils.SubmitMultiBody(url, taskID, bodyList, sign) + if err != nil { + s.saveOutTaskLog(outTask, product.ID, isbn, product.LiveImage, waveTaskDetail.PlannedQuantity, salePrice, cost, fmt.Sprintf("请求外部接口失败: %v", err), db) + return fmt.Errorf("请求外部接口失败: %v", err) + } + + var resData systemRes.ExternalAPIResponse + if err := json.Unmarshal([]byte(resp), &resData); err != nil { + return fmt.Errorf("解析响应失败: %v", err) + } + + if resData.Code != "200" { + s.saveOutTaskLog(outTask, product.ID, isbn, product.LiveImage, waveTaskDetail.PlannedQuantity, salePrice, cost, fmt.Sprintf("外部接口返回错误: code=%s, msg=%s", resData.Code, resData.Msg), db) + return fmt.Errorf("外部接口返回错误: code=%s, msg=%s", resData.Code, resData.Msg) + } + + s.saveOutTaskLog(outTask, product.ID, isbn, product.LiveImage, waveTaskDetail.PlannedQuantity, salePrice, cost, "成功", db) + + return nil +} // saveOutTaskLog 保存外部任务日志 func (s *ProductService) saveOutTaskLog(outTask models.OutTask, productID int64, isbn string, liveImage []byte, stock, salePrice, cost int64, msg string, db *gorm.DB) { @@ -2687,6 +2948,45 @@ func (s *ProductService) convertProductLogToItem(log models.ProductLog) systemRe } } +// parseImageList 解析图片列表,支持JSON数组和逗号分隔字符串两种格式 +func (s *ProductService) parseImageList(liveImage datatypes.JSON) []string { + if liveImage == nil || len(liveImage) == 0 { + return []string{} + } + + // 尝试解析为字符串数组 + var imgList []string + if err := json.Unmarshal(liveImage, &imgList); err == nil { + // 成功解析为数组,过滤空字符串后返回 + result := make([]string, 0, len(imgList)) + for _, img := range imgList { + trimmed := strings.TrimSpace(img) + if trimmed != "" { + result = append(result, trimmed) + } + } + return result + } + + // 如果解析失败,尝试解析为字符串,然后按逗号分割 + var imgStr string + if err := json.Unmarshal(liveImage, &imgStr); err == nil { + // 去除空格并按逗号分割 + parts := strings.Split(imgStr, ",") + result := make([]string, 0, len(parts)) + for _, part := range parts { + trimmed := strings.TrimSpace(part) + if trimmed != "" { + result = append(result, trimmed) + } + } + return result + } + + // 都不成功,返回空数组 + return []string{} +} + func (s *ProductService) getShopTypeName(shopType int8) string { switch shopType { case 1: