1357 lines
46 KiB
Go
1357 lines
46 KiB
Go
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)
|
||
}
|