daShangDao_psiServer/controllers/process.go

1357 lines
46 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 controllers
import (
"bytes"
"errors"
"fmt"
"gorm.io/gorm"
"io"
"mime/multipart"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"psi/constant"
"psi/database"
"psi/models"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
"strconv"
)
type ProcessApi struct{}
var processService = &service.ProcessService{}
// CreatePurchaseOrderWithWave 创建采购单并生成入库波次
func (r *ProcessApi) CreatePurchaseOrderWithWave(c *gin.Context) {
var req systemReq.PurchaseOrderCreateRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建采购单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
carService := &service.CarService{}
capacity, err := carService.GetCarCapacityByID(req.CarID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询小车容量异常", err, c, gin.H{"car_id": req.CarID})
return
}
if len(req.Items) == 0 {
items, err := bindPurchaseOrderItems(c, int(capacity))
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定采购订单项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
userInfo := utils.GetUserInfo(c)
purchaseOrderID, waveID, err := processService.CreatePurchaseOrderWithWave(req, userInfo.Username, userInfo.ID, int(capacity), database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建采购单及波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"order_id": purchaseOrderID, "wave_id": waveID}, "采购单创建成功,波次已生成", c)
}
// ReleaseWave 提交波次,生成采购订单明细和波次任务明细
func (r *ProcessApi) ReleaseWave(c *gin.Context) {
var req systemReq.WaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
carService := &service.CarService{}
capacity, err := carService.GetCarCapacityByID(req.CarID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询小车容量异常", err, c, gin.H{"car_id": req.CarID})
return
}
if len(req.Items) == 0 {
items, err := bindWaveItems(c, int(capacity))
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定波次项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
waveID, waveNo, err := processService.ReleaseWave(req, capacity, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(systemRes.WaveReleaseResponse{
WaveID: waveID,
WaveNo: waveNo,
}, "波次提交成功,任务和明细已生成", c)
}
// BindWave 绑定波次,创建入库单
func (r *ProcessApi) BindWave(c *gin.Context) {
var req systemReq.BindWaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "绑定波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
receivingOrderID, waveTaskID, waveTaskBatchNo, err := processService.BindWave(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "绑定波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(map[string]interface{}{
"receiving_order_id": receivingOrderID, // 入库单ID
"wave_task_id": waveTaskID, // 波次任务ID
"wave_task_batch_no": waveTaskBatchNo, // 波次任务批次号
}, "绑定波次成功,入库单已创建", c)
}
// GetWaveTaskInfo 获取波次任务信息
func (r *ProcessApi) GetWaveTaskInfo(c *gin.Context) {
id, err := parsePathID(c, "id", "获取波次任务信息")
if err != nil {
return
}
info, err := processService.GetWaveTaskInfo(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取波次任务信息异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(info, "获取成功", c)
}
// SubmitReceiving 提交入库
func (r *ProcessApi) SubmitReceiving(c *gin.Context) {
var req systemReq.ReceivingSubmitRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交入库请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.Items) == 0 {
databaseConn := database.GetDB(c)
var waveTask models.WaveTask
if err := databaseConn.Where("id = ? AND is_del = 0", req.WaveTaskID).First(&waveTask).Error; err != nil {
logAndFail(constant.LoggerChannelWork, "查询波次任务失败", "错误: "+err.Error(), c)
return
}
carCapacity := int(waveTask.CarCapacity)
if carCapacity <= 0 {
logAndFail(constant.LoggerChannelWork, "波次任务未设置小车容量", fmt.Sprintf("wave_task_id: %d", req.WaveTaskID), c)
return
}
items, err := bindReceivingItems(c, carCapacity)
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定入库项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
userInfo := utils.GetUserInfo(c)
err := processService.SubmitReceiving(req, userInfo.Username, userInfo.ID, userInfo.AboutID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交入库异常", err, c, req)
return
}
// TODO: 添加入库单信息同步到主库的逻辑
// 同步入库单信息到主库 product_book_xx 分表
r.syncProductBookToMainDB(req, userInfo, c)
//itemProductIDs := make([]int64, len(req.Items))
//for i, item := range req.Items {
// itemProductIDs[i] = item.ProductID
//}
//fmt.Printf("[DEBUG] itemProductIDs: %v\n", itemProductIDs)
//
//fmt.Printf("[DEBUG] 获取租户数据库, about_id: %d\n", userInfo.AboutID)
//tenantDB, err := database.GetTenantDB(userInfo.AboutID)
//if err != nil {
// fmt.Printf("[DEBUG] 获取租户数据库失败: %v\n", err)
// utils.FailWithRequestLog(constant.LoggerChannelWork, "获取租户数据库连接失败", err, c, gin.H{"about_id": userInfo.AboutID})
// return
//}
//fmt.Println("[DEBUG] 租户数据库连接成功")
//
//fmt.Printf("[DEBUG] 查询入库单(租户库), receiving_order_id: %d\n", req.ReceivingOrderID)
//var receivingOrder models.ReceivingOrder
//if err := tenantDB.Where("id = ? AND is_del = 0", req.ReceivingOrderID).First(&receivingOrder).Error; err != nil {
// fmt.Printf("[DEBUG] 查询入库单失败: %v\n", err)
// utils.FailWithRequestLog(constant.LoggerChannelWork, "查询入库单失败", err, c, gin.H{"receiving_order_id": req.ReceivingOrderID})
// return
//}
//fmt.Printf("[DEBUG] 入库单查询成功, warehouse_id: %d\n", receivingOrder.WarehouseID)
//
//fmt.Printf("[DEBUG] 查询仓库(租户库), warehouse_id: %d\n", receivingOrder.WarehouseID)
//var warehouse models.Warehouse
//if err := tenantDB.Where("id = ? AND is_del = 0", receivingOrder.WarehouseID).First(&warehouse).Error; err != nil {
// fmt.Printf("[DEBUG] 查询仓库失败: %v\n", err)
// utils.FailWithRequestLog(constant.LoggerChannelWork, "查询仓库失败", err, c, gin.H{"warehouse_id": receivingOrder.WarehouseID})
// return
//}
//fmt.Printf("[DEBUG] 仓库查询成功, warehouse_name: %s\n", warehouse.Name)
//
//fmt.Printf("[DEBUG] 获取租户数据库, about_id: %d\n", userInfo.AboutID)
//tenantDB, err = database.GetTenantDB(userInfo.AboutID)
//if err != nil {
// fmt.Printf("[DEBUG] 获取租户数据库失败: %v\n", err)
// utils.FailWithRequestLog(constant.LoggerChannelWork, "获取租户数据库连接失败", err, c, gin.H{"about_id": userInfo.AboutID})
// return
//}
//fmt.Println("[DEBUG] 租户数据库连接成功")
//
//var products []models.Product
//fmt.Printf("[DEBUG] 查询商品列表, product_ids: %v\n", itemProductIDs)
//if err := tenantDB.Where("id IN ? AND is_del = 0", itemProductIDs).Find(&products).Error; err != nil {
// fmt.Printf("[DEBUG] 查询商品列表失败: %v\n", err)
// utils.FailWithRequestLog(constant.LoggerChannelWork, "查询商品列表失败", err, c, gin.H{"product_ids": itemProductIDs})
// return
//}
//fmt.Printf("[DEBUG] 商品查询成功, 共 %d 条\n", len(products))
//
//type locationInfo struct {
// LocationID int64
// Code string
//}
//locationMap := make(map[int64]locationInfo)
//fmt.Println("[DEBUG] 开始查询库位信息")
//for _, product := range products {
// var inventoryDetail models.InventoryDetail
// if err := tenantDB.Where("product_id = ? AND warehouse_id = ? AND is_del = 0", product.ID, receivingOrder.WarehouseID).First(&inventoryDetail).Error; err == nil {
// var location models.Location
// if err := tenantDB.Where("id = ? AND is_del = 0", inventoryDetail.LocationID).First(&location).Error; err == nil {
// locationMap[product.ID] = locationInfo{
// LocationID: location.ID,
// Code: location.Code,
// }
// }
// }
//}
//fmt.Printf("[DEBUG] 库位查询完成, locationMap: %v\n", locationMap)
//
//bookInfoMap := make(map[int64]models.BookInfo)
//fmt.Println("[DEBUG] 开始查询BookInfo")
//for _, product := range products {
// if product.StandardProductID > 0 {
// if _, exists := bookInfoMap[product.StandardProductID]; !exists {
// var bookInfo models.BookInfo
// if err := database.DB.Where("id = ?", product.StandardProductID).First(&bookInfo).Error; err == nil {
// bookInfoMap[product.StandardProductID] = bookInfo
// }
// }
// }
//}
//fmt.Printf("[DEBUG] BookInfo查询完成, bookInfoMap数量: %d\n", len(bookInfoMap))
//
//fmt.Println("[DEBUG] 开始写入product_book分表")
//for _, product := range products {
// isbn := product.Barcode
// if isbn == "" {
// fmt.Printf("[DEBUG] 跳过无条码商品, product_id: %d\n", product.ID)
// continue
// }
//
// tableName := models.ProductBookTableName(isbn)
// fmt.Printf("[DEBUG] 处理商品, product_id: %d, isbn: %s, table: %s\n", product.ID, isbn, tableName)
//
// var locInfo locationInfo
// if li, exists := locationMap[product.ID]; exists {
// locInfo = li
// }
//
// var bookInfo models.BookInfo
// if bi, exists := bookInfoMap[product.StandardProductID]; exists {
// bookInfo = bi
// }
//
// catID := bookInfo.CatID
// if catID == nil {
// catID = datatypes.JSON("[]")
// }
// liveImage := product.LiveImage
// if liveImage == nil {
// liveImage = datatypes.JSON("{}")
// }
//
// now := time.Now().Unix()
//
// fmt.Printf("[DEBUG] 查重, self_id: %d, about_id: %d\n", product.ID, userInfo.AboutID)
// var existingBook models.ProductBook
// err := database.DB.Table(tableName).Where("self_id = ? AND about_id = ? AND is_del = 0", product.ID, userInfo.AboutID).First(&existingBook).Error
//
// if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
// fmt.Println("[DEBUG] 记录不存在,执行新增")
// bookRecord := models.ProductBook{
// SelfID: product.ID,
// AboutId: userInfo.AboutID,
// WarehouseID: warehouse.ID,
// WarehouseName: warehouse.Name,
// LocationID: locInfo.LocationID,
// LocationName: locInfo.Code,
// CategoryID: product.CategoryID,
// StandardProductID: product.StandardProductID,
// Fid: bookInfo.Fid,
// Type: bookInfo.Type,
// ISBN: isbn,
// FISBN: bookInfo.FISBN,
// BookName: bookInfo.BookName,
// FBookName: bookInfo.FBookName,
// Author: bookInfo.Author,
// Publishing: bookInfo.Publishing,
// PublicationTime: bookInfo.PublicationTime,
// Binding: bookInfo.Binding,
// PagesCount: bookInfo.PagesCount,
// WordsCount: bookInfo.WordsCount,
// Format: bookInfo.Format,
// CatID: catID,
// Name: product.Name,
// Appearance: product.Appearance,
// Barcode: product.Barcode,
// Price: product.Price,
// SalePrice: product.SalePrice,
// Cost: product.Cost,
// LiveImage: liveImage,
// IsBatchManaged: product.IsBatchManaged,
// IsShelfLifeManaged: product.IsShelfLifeManaged,
// Status: product.Status,
// CreatedAt: now,
// UpdatedAt: now,
// IsDel: 0,
// }
// if createErr := database.DB.Table(tableName).Create(&bookRecord).Error; createErr != nil {
// fmt.Printf("[DEBUG] 新增失败: %v\n", createErr)
// stmt := database.DB.Session(&gorm.Session{DryRun: true}).Table(tableName).Create(&bookRecord)
// utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
// "source": "写入product_book分表失败",
// "user_id": userInfo.AboutID,
// "self_id": product.ID,
// "isbn": isbn,
// "table_name": tableName,
// "warehouse_id": warehouse.ID,
// "sql": stmt.Statement.SQL.String(),
// "vars": fmt.Sprintf("%v", stmt.Statement.Vars),
// "err_msg": createErr.Error(),
// })
// utils.FailWithRequestLog(constant.LoggerChannelWork, "写入product_book分表失败", createErr, c, gin.H{
// "product_id": product.ID,
// "isbn": isbn,
// "table_name": tableName,
// })
// return
// }
// fmt.Println("[DEBUG] 新增成功")
// } else if err == nil {
// fmt.Println("[DEBUG] 记录已存在,执行更新")
// updateData := map[string]interface{}{
// "warehouse_id": warehouse.ID,
// "warehouse_name": warehouse.Name,
// "location_id": locInfo.LocationID,
// "location_name": locInfo.Code,
// "category_id": product.CategoryID,
// "standard_product_id": product.StandardProductID,
// "name": product.Name,
// "book_name": bookInfo.BookName,
// "f_isbn": bookInfo.FISBN,
// "f_book_name": bookInfo.FBookName,
// "author": bookInfo.Author,
// "publishing": bookInfo.Publishing,
// "publication_time": bookInfo.PublicationTime,
// "binding": bookInfo.Binding,
// "pages_count": bookInfo.PagesCount,
// "words_count": bookInfo.WordsCount,
// "format": bookInfo.Format,
// "cat_id": catID,
// "appearance": product.Appearance,
// "barcode": product.Barcode,
// "price": product.Price,
// "sale_price": product.SalePrice,
// "cost": product.Cost,
// "live_image": liveImage,
// "is_batch_managed": product.IsBatchManaged,
// "is_shelf_life_managed": product.IsShelfLifeManaged,
// "status": product.Status,
// "updated_at": now,
// }
// if updateErr := database.DB.Table(tableName).Model(&existingBook).Updates(updateData).Error; updateErr != nil {
// fmt.Printf("[DEBUG] 更新失败: %v\n", updateErr)
// utils.FailWithRequestLog(constant.LoggerChannelWork, "更新product_book分表失败", updateErr, c, gin.H{
// "product_id": product.ID,
// "isbn": isbn,
// "table_name": tableName,
// })
// return
// }
// fmt.Println("[DEBUG] 更新成功")
// } else {
// fmt.Printf("[DEBUG] 查询分表异常: %v\n", err)
// utils.FailWithRequestLog(constant.LoggerChannelWork, "查询product_book分表失败", err, c, gin.H{
// "product_id": product.ID,
// "isbn": isbn,
// "table_name": tableName,
// })
// return
// }
//}
//fmt.Println("[DEBUG] product_book分表写入全部完成")
fmt.Printf("提交入库成功入库单ID: %d, 波次任务ID: %d, 波次任务批次号: %s\n", req.ReceivingOrderID, req.WaveTaskID)
go func(receivingOrderID, aboutID, waveTaskID int64, itemProductIDs []int64) {
databaseConn := database.DB
var receivingOrder models.ReceivingOrder
if err := databaseConn.Where("id = ? AND is_del = 0", receivingOrderID).First(&receivingOrder).Error; err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "查询入库单失败",
"receiving_order_id": receivingOrderID,
"err_msg": err.Error(),
})
return
}
var warehouse models.Warehouse
if err := databaseConn.Where("id = ? AND is_del = 0", receivingOrder.WarehouseID).First(&warehouse).Error; err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "查询仓库失败",
"warehouse_id": receivingOrder.WarehouseID,
"err_msg": err.Error(),
})
return
}
// 同步给主库对应的product_book_xx中
// 获取租户数据库连接
tenantDB, err := database.GetTenantDB(aboutID)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "获取租户数据库连接失败",
"user_id": aboutID,
"err_msg": err.Error(),
})
return
}
// 查询商品详细信息
var products []models.Product
if err := tenantDB.Where("id IN ? AND is_del = 0", itemProductIDs).Find(&products).Error; err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "查询商品列表失败",
"user_id": aboutID,
"product_ids": itemProductIDs,
"err_msg": err.Error(),
})
return
}
// 查询库位信息获取库位ID和Code
type locationInfo struct {
LocationID int64
Code string
}
locationMap := make(map[int64]locationInfo)
for _, product := range products {
var inventoryDetail models.InventoryDetail
if err := tenantDB.Where("product_id = ? AND warehouse_id = ? AND is_del = 0", product.ID, receivingOrder.WarehouseID).First(&inventoryDetail).Error; err == nil {
var location models.Location
if err := tenantDB.Where("id = ? AND is_del = 0", inventoryDetail.LocationID).First(&location).Error; err == nil {
locationMap[product.ID] = locationInfo{
LocationID: location.ID,
Code: location.Code,
}
}
}
}
// 查询BookInfo按StandardProductID关联
bookInfoMap := make(map[int64]models.BookInfo)
for _, product := range products {
if product.StandardProductID > 0 {
if _, exists := bookInfoMap[product.StandardProductID]; !exists {
var bookInfo models.BookInfo
if err := database.DB.Where("id = ?", product.StandardProductID).First(&bookInfo).Error; err == nil {
bookInfoMap[product.StandardProductID] = bookInfo
}
}
}
}
// 逐个写入主库 product_book_xx 分表
for _, product := range products {
isbn := product.Barcode
if isbn == "" {
continue
}
tableName := models.ProductBookTableName(isbn)
var locInfo locationInfo
if li, exists := locationMap[product.ID]; exists {
locInfo = li
}
var bookInfo models.BookInfo
if bi, exists := bookInfoMap[product.StandardProductID]; exists {
bookInfo = bi
}
now := time.Now().Unix()
// 按 self_id + about_id 查重避免不同租户商品ID冲突
var existingBook models.ProductBook
err := database.DB.Table(tableName).Where("self_id = ? AND about_id = ? AND is_del = 0", product.ID, aboutID).First(&existingBook).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
bookRecord := models.ProductBook{
SelfID: product.ID,
AboutId: aboutID,
WarehouseID: warehouse.ID,
WarehouseName: warehouse.Name,
LocationID: locInfo.LocationID,
LocationName: locInfo.Code,
CategoryID: product.CategoryID,
StandardProductID: product.StandardProductID,
Fid: bookInfo.Fid,
Type: bookInfo.Type,
ISBN: isbn,
FISBN: bookInfo.FISBN,
BookName: bookInfo.BookName,
FBookName: bookInfo.FBookName,
Author: bookInfo.Author,
Publishing: bookInfo.Publishing,
PublicationTime: bookInfo.PublicationTime,
Binding: bookInfo.Binding,
PagesCount: bookInfo.PagesCount,
WordsCount: bookInfo.WordsCount,
Format: bookInfo.Format,
CatID: bookInfo.CatID,
Name: product.Name,
Appearance: product.Appearance,
Barcode: product.Barcode,
Price: product.Price,
SalePrice: product.SalePrice,
Cost: product.Cost,
LiveImage: product.LiveImage,
IsBatchManaged: product.IsBatchManaged,
IsShelfLifeManaged: product.IsShelfLifeManaged,
Status: product.Status,
CreatedAt: now,
UpdatedAt: now,
IsDel: 0,
}
if createErr := database.DB.Table(tableName).Create(&bookRecord).Error; createErr != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "写入product_book分表失败",
"user_id": aboutID,
"self_id": product.ID,
"isbn": isbn,
"table_name": tableName,
"warehouse_id": warehouse.ID,
"err_msg": createErr.Error(),
})
} else {
utils.InfoLog(constant.LoggerChannelWork, logrus.Fields{
"source": "写入product_book分表成功",
"user_id": aboutID,
"self_id": product.ID,
"isbn": isbn,
"table_name": tableName,
"warehouse_id": warehouse.ID,
"location_id": locInfo.LocationID,
})
}
} else if err == nil {
updateData := map[string]interface{}{
"warehouse_id": warehouse.ID,
"warehouse_name": warehouse.Name,
"location_id": locInfo.LocationID,
"location_name": locInfo.Code,
"category_id": product.CategoryID,
"standard_product_id": product.StandardProductID,
"name": product.Name,
"book_name": bookInfo.BookName,
"f_isbn": bookInfo.FISBN,
"f_book_name": bookInfo.FBookName,
"author": bookInfo.Author,
"publishing": bookInfo.Publishing,
"publication_time": bookInfo.PublicationTime,
"binding": bookInfo.Binding,
"pages_count": bookInfo.PagesCount,
"words_count": bookInfo.WordsCount,
"format": bookInfo.Format,
"cat_id": bookInfo.CatID,
"appearance": product.Appearance,
"barcode": product.Barcode,
"price": product.Price,
"sale_price": product.SalePrice,
"cost": product.Cost,
"live_image": product.LiveImage,
"is_batch_managed": product.IsBatchManaged,
"is_shelf_life_managed": product.IsShelfLifeManaged,
"status": product.Status,
"updated_at": now,
}
if updateErr := database.DB.Table(tableName).Model(&existingBook).Updates(updateData).Error; updateErr != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "更新product_book分表失败",
"user_id": aboutID,
"self_id": product.ID,
"isbn": isbn,
"table_name": tableName,
"warehouse_id": warehouse.ID,
"err_msg": updateErr.Error(),
})
} else {
utils.InfoLog(constant.LoggerChannelWork, logrus.Fields{
"source": "更新product_book分表成功",
"user_id": aboutID,
"self_id": product.ID,
"isbn": isbn,
"table_name": tableName,
"warehouse_id": warehouse.ID,
"location_id": locInfo.LocationID,
})
}
} else {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "查询product_book分表失败",
"user_id": aboutID,
"self_id": product.ID,
"isbn": isbn,
"table_name": tableName,
"err_msg": err.Error(),
})
}
}
warehouseId := warehouse.ID
productIds := make([]string, len(itemProductIDs))
for i, pid := range itemProductIDs {
productIds[i] = fmt.Sprintf("%d", pid)
}
productIdsStr := strings.Join(productIds, ",")
url := "https://erp.buzhiyushu.cn/zhishu/product/releaseGoodsAuto"
method := "POST"
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("userId", fmt.Sprintf("%d", aboutID))
_ = writer.WriteField("warehouseId", fmt.Sprintf("%d", warehouseId))
_ = writer.WriteField("productId", productIdsStr)
closeErr := writer.Close()
if closeErr != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "关闭multipart writer失败",
"err_msg": closeErr.Error(),
})
return
}
client := &http.Client{}
httpReq, err := http.NewRequest(method, url, payload)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "创建HTTP请求失败",
"err_msg": err.Error(),
})
return
}
httpReq.Header.Add("Authorization", "Basic ZWxhc3RpYzo1bVJESVVnNTJWQzBmcDE0bnctRg==")
httpReq.Header.Set("Content-Type", writer.FormDataContentType())
res, err := client.Do(httpReq)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "调用外部API失败",
"err_msg": err.Error(),
})
return
}
defer func(Body io.ReadCloser) {
closeBodyErr := Body.Close()
if closeBodyErr != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "调用外部API失败-Body关闭失败",
"err_msg": closeBodyErr.Error(),
})
}
}(res.Body)
body, err := io.ReadAll(res.Body)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "读取响应失败",
"err_msg": err.Error(),
})
return
}
/*payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("userId", fmt.Sprintf("%d", aboutID))
_ = writer.WriteField("warehouseId", fmt.Sprintf("%d", warehouseId))
_ = writer.WriteField("productId", productIdsStr)
err := writer.Close()
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "关闭multipart writer失败",
"err_msg": err.Error(),
})
return
}
client := &http.Client{}
req, err := http.NewRequest(method, url, payload)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "创建HTTP请求失败",
"err_msg": err.Error(),
})
return
}
req.Header.Add("Authorization", "Basic ZWxhc3RpYzo1bVJESVVnNTJWQzBmcDE0bnctRg==")
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := client.Do(req)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "调用外部API失败",
"err_msg": err.Error(),
})
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "调用外部API失败-Body关闭失败",
"err_msg": err.Error(),
})
}
}(res.Body)
body, err := io.ReadAll(res.Body)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "读取响应失败",
"err_msg": err.Error(),
})
return
}*/
utils.InfoLog(constant.LoggerChannelWork, logrus.Fields{
"source": "外部API调用完成",
"user_id": aboutID,
"warehouse_id": warehouseId,
"product_ids": productIdsStr,
"status_code": res.StatusCode,
"response": string(body),
})
}(req.ReceivingOrderID, userInfo.AboutID, req.WaveTaskID, func() []int64 {
productIDs := make([]int64, len(req.Items))
for i, item := range req.Items {
productIDs[i] = item.ProductID
}
return productIDs
}())
systemRes.OkWithMessage("入库提交成功", c)
}
// GetReceivingDetail 获取入库单详情
func (r *ProcessApi) GetReceivingDetail(c *gin.Context) {
id, err := parsePathID(c, "id", "获取入库单详情")
if err != nil {
return
}
detail, err := processService.GetReceivingDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取入库单详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(detail, "获取成功", c)
}
// CreateSalesOrderWithDetail 创建销售订单和明细
func (r *ProcessApi) CreateSalesOrderWithDetail(c *gin.Context) {
var req systemReq.SalesOrderCreateRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建销售订单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.Items) == 0 {
items, err := bindSalesOrderItems(c)
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定销售订单项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
salesOrderID, err := processService.CreateSalesOrderWithDetail(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建销售订单及波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"order_id": salesOrderID}, "销售订单创建成功", c)
}
// CreateOutboundOrder 基于多个销售订单创建出库单
func (r *ProcessApi) CreateOutboundOrder(c *gin.Context) {
var req systemReq.CreateOutboundOrderRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建出库单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.SalesOrderIDs) == 0 {
ids, err := parseIdsFrom(c, req.Total)
if err != nil {
systemRes.FailWithMessage("参数错误: "+err.Error(), c)
return
}
req.SalesOrderIDs = ids
}
userInfo := utils.GetUserInfo(c)
outboundOrderID, outNo, err := processService.CreateOutboundOrder(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建出库单异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{
"outbound_order_id": outboundOrderID,
"out_no": outNo,
}, "出库单创建成功", c)
}
// CreateOutboundWave 基于出库单创建出库波次
func (r *ProcessApi) CreateOutboundWave(c *gin.Context) {
var req systemReq.CreateOutboundWaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if req.OutboundOrderID == 0 {
systemRes.FailWithMessage("参数错误: 出库单ID不能为空", c)
return
}
userInfo := utils.GetUserInfo(c)
waveID, err := processService.CreateOutboundWave(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建出库波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(systemRes.WaveReleaseResponse{
WaveID: waveID,
}, "出库波次创建成功", c)
}
// ReleaseOutboundWave 提交出库波次,生成波次任务明细
func (r *ProcessApi) ReleaseOutboundWave(c *gin.Context) {
var req systemReq.WaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
waveID, waveNo, err := processService.ReleaseOutboundWave(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交出库波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(systemRes.WaveReleaseResponse{
WaveID: waveID,
WaveNo: waveNo,
}, "出库波次提交成功,任务和明细已生成", c)
}
// BindOutboundWave 绑定出库波次
func (r *ProcessApi) BindOutboundWave(c *gin.Context) {
var req systemReq.BindWaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "绑定出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
outboundOrderID, waveTaskID, waveTaskBatchNo, err := processService.BindOutboundWave(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "绑定出库波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(map[string]interface{}{
"out_order_id": outboundOrderID,
"wave_task_id": waveTaskID,
"wave_task_batch_no": waveTaskBatchNo,
}, "绑定出库波次成功", c)
}
// SubmitOutbound 提交出库
func (r *ProcessApi) SubmitOutbound(c *gin.Context) {
var req systemReq.OutboundSubmitRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交出库请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.Items) == 0 {
databaseConn := database.GetDB(c)
var waveTask models.WaveTask
if err := databaseConn.Where("id = ? AND is_del = 0", req.WaveTaskID).First(&waveTask).Error; err != nil {
logAndFail(constant.LoggerChannelWork, "查询波次任务失败", "错误: "+err.Error(), c)
return
}
var count int64
if err := databaseConn.Model(&models.WaveTaskDetail{}).Where("wave_task_id = ? AND status = 1 AND is_del = 0", waveTask.ID).Count(&count).Error; err != nil {
logAndFail(constant.LoggerChannelWork, "查询波次任务明细数量失败", "错误: "+err.Error(), c)
return
}
items, err := bindOutboundItems(c, int(count))
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定出库项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
userInfo := utils.GetUserInfo(c)
err := processService.SubmitOutbound(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交出库异常", err, c, req)
return
}
systemRes.OkWithMessage("出库提交成功", c)
}
// GetOutboundDetail 获取发货单详情
func (r *ProcessApi) GetOutboundDetail(c *gin.Context) {
id, err := parsePathID(c, "id", "获取发货单详情")
if err != nil {
return
}
detail, err := processService.GetOutboundDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取发货单详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(detail, "获取成功", c)
}
// CreateShippingOrder 基于多个出库单创建发货单
func (r *ProcessApi) CreateShippingOrder(c *gin.Context) {
var req systemReq.CreateShippingOrderRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建发货单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.OutboundOrderIDs) == 0 {
ids, err := parseIdsFrom(c, req.Total)
if err != nil {
systemRes.FailWithMessage("参数错误: "+err.Error(), c)
return
}
req.OutboundOrderIDs = ids
}
userInfo := utils.GetUserInfo(c)
shippingOrderID, shippingNo, err := processService.CreateShippingOrder(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建发货单异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{
"shipping_order_id": shippingOrderID,
"shipping_no": shippingNo,
}, "发货单创建成功", c)
}
// UpdateShippingLogistics 更新发货单物流信息并回填销售订单明细
func (r *ProcessApi) UpdateShippingLogistics(c *gin.Context) {
var req systemReq.UpdateShippingLogisticsRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新发货单物流信息请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := processService.UpdateShippingLogistics(req, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新发货单物流信息异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
// CancelSalesOrder 取消销售订单
func (r *ProcessApi) CancelSalesOrder(c *gin.Context) {
var req systemReq.CancelSalesOrderRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "取消销售订单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := processService.CancelSalesOrder(req.OrderID, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "取消销售订单异常", err, c, req)
return
}
systemRes.OkWithMessage("销售订单取消成功,库存已释放", c)
}
// CancelOutboundWave 取消出库波次
func (r *ProcessApi) CancelOutboundWave(c *gin.Context) {
var req systemReq.CancelOutboundWaveRequest
if err := c.ShouldBindJSON(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "取消出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := processService.CancelOutboundWave(req.WaveID, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "取消出库波次异常", err, c, req)
return
}
systemRes.OkWithMessage("出库波次取消成功,库存已释放", c)
}
// AdjustInventory 盘库调整(加库存/减库存)
func (r *ProcessApi) AdjustInventory(c *gin.Context) {
var req systemReq.StockCheckAdjustRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "盘库调整请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := processService.AdjustInventory(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "盘库调整异常", err, c, req)
return
}
systemRes.OkWithMessage("盘库调整成功", c)
}
// ReturnInventory 盘库退货
func (r *ProcessApi) ReturnInventory(c *gin.Context) {
var req systemReq.StockCheckReturnRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "盘库退货请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := processService.ReturnInventory(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "盘库退货异常", err, c, req)
return
}
systemRes.OkWithMessage("盘库退货成功", c)
}
// ChangeLocation 出库单切换库位
func (r *ProcessApi) ChangeLocation(c *gin.Context) {
var req systemReq.ChangeLocationRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "出库单切换库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := processService.ChangeLocation(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
if errors.Is(err, utils.ErrNoAvailableLocation) {
c.Status(http.StatusNoContent)
return
}
utils.FailWithRequestLog(constant.LoggerChannelWork, "出库单切换库位异常", err, c, req)
return
}
systemRes.OkWithMessage("库位切换成功", c)
}
// parsePathID 从URL路径参数中获取并验证ID
func parsePathID(c *gin.Context, paramName, source string) (int64, error) {
idStr := c.Param(paramName)
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": source + "请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return 0, fmt.Errorf("ID参数不能为空")
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": source + "请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return 0, fmt.Errorf("ID格式错误")
}
return id, nil
}
// parseOptionalDate 解析可选日期字段
func parseOptionalDate(c *gin.Context, key string) int64 {
if str := c.PostForm(key); str != "" {
if val, err := strconv.ParseInt(str, 10, 64); err == nil {
return val
}
}
return 0
}
// bindSimpleItem 绑定简单项ProductID + Quantity + UnitPrice
func bindSimpleItem[T any](c *gin.Context, i int, creator func(int64, int64, int64) T) (T, bool, error) {
var zero T
productID, hasProduct, err := getPostFormInt64(c, fmt.Sprintf("items[%d][product_id]", i))
if err != nil || !hasProduct {
return zero, false, err
}
quantity, hasQuantity, err := getPostFormInt64(c, fmt.Sprintf("items[%d][quantity]", i))
if err != nil || !hasQuantity {
return zero, false, err
}
unitPrice, hasPrice, err := getPostFormInt64(c, fmt.Sprintf("items[%d][unit_price]", i))
if err != nil || !hasPrice {
return zero, false, err
}
return creator(productID, quantity, unitPrice), true, nil
}
// bindLocationItem 绑定带位置信息的项ProductID + LocationID + Quantity + 批次信息)
func bindLocationItem[T any](c *gin.Context, i int, creator func(int64, int64, string, int64, int64, int64) T) (T, bool, error) {
var zero T
productID, hasProduct, err := getPostFormInt64(c, fmt.Sprintf("items[%d][product_id]", i))
if err != nil || !hasProduct {
return zero, false, err
}
locationID, hasLocation, err := getPostFormInt64(c, fmt.Sprintf("items[%d][location_id]", i))
if err != nil || !hasLocation {
return zero, false, err
}
quantity, hasQuantity, err := getPostFormInt64(c, fmt.Sprintf("items[%d][quantity]", i))
if err != nil || !hasQuantity {
return zero, false, err
}
batchNo := c.PostForm(fmt.Sprintf("items[%d][batch_no]", i))
productionDate := parseOptionalDate(c, fmt.Sprintf("items[%d][production_date]", i))
expiryDate := parseOptionalDate(c, fmt.Sprintf("items[%d][expiry_date]", i))
return creator(productID, locationID, batchNo, productionDate, expiryDate, quantity), true, nil
}
// bindPurchaseOrderItems 绑定采购订单项
func bindPurchaseOrderItems(c *gin.Context, capacity int) ([]systemReq.PurchaseOrderItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.PurchaseOrderItemRequest, bool, error) {
return bindSimpleItem(c, i, func(productID, quantity, unitPrice int64) systemReq.PurchaseOrderItemRequest {
return systemReq.PurchaseOrderItemRequest{
ProductID: productID,
Quantity: quantity,
UnitPrice: unitPrice,
}
})
}, capacity)
}
// bindWaveItems 绑定波次项
func bindWaveItems(c *gin.Context, capacity int) ([]systemReq.WaveItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.WaveItemRequest, bool, error) {
return bindSimpleItem(c, i, func(productID, quantity, unitPrice int64) systemReq.WaveItemRequest {
return systemReq.WaveItemRequest{
ProductID: productID,
Quantity: quantity,
UnitPrice: unitPrice,
}
})
}, capacity)
}
// bindReceivingItems 绑定入库项
func bindReceivingItems(c *gin.Context, maxItems int) ([]systemReq.ReceivingItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.ReceivingItemRequest, bool, error) {
return bindLocationItem(c, i, func(productID, locationID int64, batchNo string, productionDate, expiryDate, quantity int64) systemReq.ReceivingItemRequest {
return systemReq.ReceivingItemRequest{
ProductID: productID,
LocationID: locationID,
BatchNo: batchNo,
ProductionDate: productionDate,
ExpiryDate: expiryDate,
Quantity: quantity,
}
})
}, maxItems)
}
// bindSalesOrderItems 绑定销售订单项
func bindSalesOrderItems(c *gin.Context) ([]systemReq.SalesOrderItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.SalesOrderItemRequest, bool, error) {
return bindSimpleItem(c, i, func(productID, quantity, unitPrice int64) systemReq.SalesOrderItemRequest {
return systemReq.SalesOrderItemRequest{
ProductID: productID,
Quantity: quantity,
UnitPrice: unitPrice,
}
})
}, 200)
}
// bindOutboundItems 绑定出库项
func bindOutboundItems(c *gin.Context, maxItems int) ([]systemReq.OutboundItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.OutboundItemRequest, bool, error) {
return bindLocationItem(c, i, func(productID, locationID int64, batchNo string, productionDate, expiryDate, quantity int64) systemReq.OutboundItemRequest {
return systemReq.OutboundItemRequest{
ProductID: productID,
LocationID: locationID,
BatchNo: batchNo,
ProductionDate: productionDate,
ExpiryDate: expiryDate,
Quantity: quantity,
}
})
}, maxItems)
}
// bindItemsWithForm 通用表单项绑定函数
func bindItemsWithForm[T any](c *gin.Context, builder func(int) (T, bool, error), maxItems int) ([]T, error) {
items := make([]T, 0)
for i := 0; i < maxItems; i++ {
item, hasData, err := builder(i)
if err != nil {
return nil, err
}
if !hasData {
continue
}
items = append(items, item)
}
return items, nil
}
func parseIdsFrom(c *gin.Context, total int) ([]int64, error) {
var ids []int64
for i := 0; i < total; i++ {
idStr := c.PostForm(fmt.Sprintf("order_ids[%d]", i))
if idStr == "" {
break
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
return nil, fmt.Errorf("第%d个ID格式错误", i)
}
if id <= 0 {
return nil, fmt.Errorf("第%d个ID必须大于0", i)
}
ids = append(ids, id)
}
if len(ids) == 0 {
return nil, fmt.Errorf("至少需要一个ID")
}
return ids, nil
}
// getPostFormInt64 获取表单中的int64值
func getPostFormInt64(c *gin.Context, key string) (int64, bool, error) {
str := c.PostForm(key)
if str == "" {
return 0, false, nil
}
val, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return 0, true, err
}
return val, true, nil
}
// logAndFail 业务统一的错误日志和响应函数
func logAndFail(channel string, source, errMsg string, c *gin.Context) {
utils.ErrorLog(channel, logrus.Fields{
"source": source,
"err_msg": errMsg,
})
systemRes.FailWithMessage(errMsg, c)
}
// ValidAndFail 参数统一的错误日志和响应函数
func ValidAndFail(channel string, source, errMsg string, c *gin.Context, err error) {
utils.ErrorLog(channel, logrus.Fields{
"source": source,
"err_msg": errMsg,
})
utils.HandleValidationError(c, err)
}