修复了查询库存接口未计算锁定库存问题

修复了同步反射商品表未同步到的问题
This commit is contained in:
xiaodongzhu825 2026-06-17 15:58:44 +08:00
parent 31f300e0b3
commit d0e9a0fa82
7 changed files with 206 additions and 85 deletions

View File

@ -4,10 +4,12 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"gorm.io/gorm"
"io" "io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -196,16 +198,6 @@ func (r *ProcessApi) SubmitReceiving(c *gin.Context) {
} }
// 同步给主库对应的product_book_xx中 // 同步给主库对应的product_book_xx中
/*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中 // 同步给主库对应的product_book_xx中
// 获取租户数据库连接 // 获取租户数据库连接
tenantDB, err := database.GetTenantDB(aboutID) tenantDB, err := database.GetTenantDB(aboutID)
@ -230,52 +222,188 @@ func (r *ProcessApi) SubmitReceiving(c *gin.Context) {
return return
} }
// 查询库位信息(用于获取库位名称) // 查询库位信息获取库位ID和Code
locationMap := make(map[int64]string) type locationInfo struct {
LocationID int64
Code string
}
locationMap := make(map[int64]locationInfo)
for _, product := range products { for _, product := range products {
// 通过库存明细表查询商品所在的库位
var inventoryDetail models.InventoryDetail 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 { 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 var location models.Location
if err := tenantDB.Where("id = ? AND is_del = 0", inventoryDetail.LocationID).First(&location).Error; err == nil { if err := tenantDB.Where("id = ? AND is_del = 0", inventoryDetail.LocationID).First(&location).Error; err == nil {
locationMap[product.ID] = location.Code locationMap[product.ID] = locationInfo{
LocationID: location.ID,
Code: location.Code,
}
} }
} }
} }
// 逐个同步商品到主库 // 查询BookInfo按StandardProductID关联
bookInfoMap := make(map[int64]models.BookInfo)
for _, product := range products { for _, product := range products {
locationCode := "" if product.StandardProductID > 0 {
if code, exists := locationMap[product.ID]; exists { if _, exists := bookInfoMap[product.StandardProductID]; !exists {
locationCode = code 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
} }
if syncErr := database.SyncProductToMainDB(aboutID, &product, warehouse.ID, warehouse.Name, 0, locationCode); syncErr != nil { tableName := models.ProductBookTableName(isbn)
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "同步商品到主库失败", var locInfo locationInfo
"user_id": aboutID, if li, exists := locationMap[product.ID]; exists {
"product_id": product.ID, locInfo = li
"warehouse_id": warehouse.ID, }
"location_code": locationCode,
"err_msg": syncErr.Error(), 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 { } else {
utils.InfoLog(constant.LoggerChannelWork, logrus.Fields{ utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "同步商品到主库成功", "source": "查询product_book分表失败",
"user_id": aboutID, "user_id": aboutID,
"product_id": product.ID, "self_id": product.ID,
"product_name": product.Name, "isbn": isbn,
"barcode": product.Barcode, "table_name": tableName,
"warehouse_id": warehouse.ID, "err_msg": err.Error(),
"location_code": locationCode,
}) })
} }
} }
warehouseId := warehouse.ID warehouseId := warehouse.ID
//warehouseId := warehouse.ID
productIds := make([]string, len(itemProductIDs)) productIds := make([]string, len(itemProductIDs))
for i, pid := range itemProductIDs { for i, pid := range itemProductIDs {
productIds[i] = fmt.Sprintf("%d", pid) productIds[i] = fmt.Sprintf("%d", pid)

View File

@ -7,7 +7,8 @@ import (
// ProductBook 商品书籍分表按ISBN后两位分表 product_book_00 ~ product_book_99 // ProductBook 商品书籍分表按ISBN后两位分表 product_book_00 ~ product_book_99
type ProductBook struct { type ProductBook struct {
ID int64 `json:"id" gorm:"primarykey;comment:商品ID"` ID int64 `json:"id" gorm:"primarykey;comment:自增ID"`
SelfID int64 `json:"self_id" gorm:"not null;default:0;comment:再分库商品表中的商品ID"`
CategoryID int64 `json:"category_id" gorm:"not null;default:0;comment:分类ID"` CategoryID int64 `json:"category_id" gorm:"not null;default:0;comment:分类ID"`
AboutId int64 `json:"about_id" gorm:"not null;default:0;index;comment:关联ID"` AboutId int64 `json:"about_id" gorm:"not null;default:0;index;comment:关联ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;index;comment:仓库ID"` WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;index;comment:仓库ID"`

View File

@ -29,25 +29,25 @@ type CreateLogisticsRequest struct {
} }
type UpdateLogisticsRequest struct { type UpdateLogisticsRequest struct {
Id uint64 `form:"id" binding:"required"` Id uint64 `form:"id" binding:"required"` // 物流模板ID
TemplateName string `form:"template_name" binding:"required,max=255"` TemplateName string `form:"template_name" binding:"required,max=255"` // 物流模板名称
DeliveryProvince string `form:"delivery_province" binding:"required,max=255"` DeliveryProvince string `form:"delivery_province" binding:"required,max=255"` // 发货省
DeliveryCity string `form:"delivery_city" binding:"required,max=255"` DeliveryCity string `form:"delivery_city" binding:"required,max=255"` // 发货市
DeliveryArea string `form:"delivery_area" binding:"required,max=255"` DeliveryArea string `form:"delivery_area" binding:"required,max=255"` // 发货区
DeliveryAddress string `form:"delivery_address" binding:"required,max=255"` DeliveryAddress string `form:"delivery_address" binding:"required,max=255"` // 发货地址
PricingMethod string `form:"pricing_method" binding:"required,oneof=0 1 2 3"` PricingMethod string `form:"pricing_method" binding:"required,oneof=0 1 2 3"` // 计费方式
Shipping string `form:"shipping" binding:"required,oneof=0 1 2"` Shipping string `form:"shipping" binding:"required,oneof=0 1 2"` // 配送方式
FirWbv float64 `form:"fir_wbv"` FirWbv float64 `form:"fir_wbv"` // 首件
FirPrice float64 `form:"fir_price"` FirPrice float64 `form:"fir_price"` // 首费
ContinueWbv float64 `form:"continue_wbv"` ContinueWbv float64 `form:"continue_wbv"` // 续件
ContinuePrice float64 `form:"continue_price"` ContinuePrice float64 `form:"continue_price"` // 续费
Contact string `form:"contact" binding:"required,max=14"` Contact string `form:"contact" binding:"required,max=14"` // 联系人
PhoneNumber uint64 `form:"phone_number" binding:"required"` PhoneNumber uint64 `form:"phone_number" binding:"required"` // 联系电话
FullAddress string `form:"full_address" binding:"required,max=255"` FullAddress string `form:"full_address" binding:"required,max=255"` // 收货地址
ShippingRange string `form:"shipping_range" binding:"required"` ShippingRange string `form:"shipping_range" binding:"required"` // 配送范围
WarehouseId uint64 `form:"warehouse_id"` WarehouseId uint64 `form:"warehouse_id"` // 仓库ID
Remark string `form:"remark" binding:"max=255"` Remark string `form:"remark" binding:"max=255"` // 备注
Status string `form:"status" binding:"oneof=0 1"` Status string `form:"status" binding:"oneof=0 1"` // 状态
} }
type DeleteLogisticsRequest struct { type DeleteLogisticsRequest struct {

View File

@ -12,13 +12,13 @@ type GetSplitAccountDeductionLogListRequest struct {
// AddSplitAccountDeductionLogRequest 添加分账扣钱日志请求 // AddSplitAccountDeductionLogRequest 添加分账扣钱日志请求
type AddSplitAccountDeductionLogRequest struct { type AddSplitAccountDeductionLogRequest struct {
BusinessNo string `form:"business_no" binding:"required"` BusinessNo string `form:"business_no" binding:"required"` // 业务单号
ConfigID int64 `form:"config_id" binding:"required"` ConfigID int64 `form:"config_id" binding:"required"` // 分账配置ID
ConfigName string `form:"config_name" binding:"required"` ConfigName string `form:"config_name" binding:"required"` // 分账配置名称
DeductionDetails string `form:"deduction_details" binding:"required"` DeductionDetails string `form:"deduction_details" binding:"required"` // 扣钱详情
TotalAmount float64 `form:"total_amount" binding:"required"` TotalAmount float64 `form:"total_amount" binding:"required"` // 总金额
DeductionAmount float64 `form:"deduction_amount" binding:"required"` DeductionAmount float64 `form:"deduction_amount" binding:"required"` // 扣钱金额
RemainingAmount float64 `form:"remaining_amount" binding:"required"` RemainingAmount float64 `form:"remaining_amount" binding:"required"` // 剩余金额
} }
// UpdateSplitAccountDeductionLogRequest 更新分账扣钱日志请求 // UpdateSplitAccountDeductionLogRequest 更新分账扣钱日志请求
@ -29,6 +29,7 @@ type UpdateSplitAccountDeductionLogRequest struct {
ConfigName string `form:"config_name"` ConfigName string `form:"config_name"`
DeductionDetails string `form:"deduction_details"` DeductionDetails string `form:"deduction_details"`
TotalAmount float64 `form:"total_amount"` TotalAmount float64 `form:"total_amount"`
Status int8 `form:"status"`
DeductionAmount float64 `form:"deduction_amount"` DeductionAmount float64 `form:"deduction_amount"`
RemainingAmount float64 `form:"remaining_amount"` RemainingAmount float64 `form:"remaining_amount"`
} }

View File

@ -12,6 +12,7 @@ type SplitAccountDeductionLog struct {
TotalAmount float64 `json:"total_amount" gorm:"type:decimal(15,2);not null;default:0.00;comment:总金额(分账前)"` TotalAmount float64 `json:"total_amount" gorm:"type:decimal(15,2);not null;default:0.00;comment:总金额(分账前)"`
DeductionAmount float64 `json:"deduction_amount" gorm:"type:decimal(15,2);not null;default:0.00;comment:扣款总金额"` DeductionAmount float64 `json:"deduction_amount" gorm:"type:decimal(15,2);not null;default:0.00;comment:扣款总金额"`
RemainingAmount float64 `json:"remaining_amount" gorm:"type:decimal(15,2);not null;default:0.00;comment:剩余金额(分账后)"` RemainingAmount float64 `json:"remaining_amount" gorm:"type:decimal(15,2);not null;default:0.00;comment:剩余金额(分账后)"`
Status int8 `json:"status" gorm:"type:tinyint;not null;default:0;comment:状态 0:正常 1:退款"`
CreatedBy string `json:"created_by" gorm:"size:100;not null;default:'';comment:创建人/系统"` CreatedBy string `json:"created_by" gorm:"size:100;not null;default:'';comment:创建人/系统"`
UpdatedBy string `json:"updated_by" gorm:"size:100;comment:更新人"` UpdatedBy string `json:"updated_by" gorm:"size:100;comment:更新人"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;default:0;comment:创建时间戳(秒)"` CreatedAt int64 `json:"created_at" gorm:"type:bigint;default:0;comment:创建时间戳(秒)"`

View File

@ -797,10 +797,7 @@ func (s *ProcessService) loadOrderAndWaveDetails(tx *gorm.DB, orderID, waveTaskI
} }
// processOrderItems 处理订单明细 // processOrderItems 处理订单明细
func (s *ProcessService) processOrderItems(tx *gorm.DB, items []orderItemInfo, orderInfo *orderInfo, func (s *ProcessService) processOrderItems(tx *gorm.DB, items []orderItemInfo, orderInfo *orderInfo, productMap map[int64]models.Product, locationMap map[int64]models.Location, orderItemMap map[int64]interface{}, waveTaskDetailMap map[int64]*models.WaveTaskDetail, operator string, operatorID int64, now int64, changeType int8) (map[inventoryKey]*inventoryOperation, []models.InventoryLog, error) {
productMap map[int64]models.Product, locationMap map[int64]models.Location,
orderItemMap map[int64]interface{}, waveTaskDetailMap map[int64]*models.WaveTaskDetail,
operator string, operatorID int64, now int64, changeType int8) (map[inventoryKey]*inventoryOperation, []models.InventoryLog, error) {
inventoryOpMap := make(map[inventoryKey]*inventoryOperation) inventoryOpMap := make(map[inventoryKey]*inventoryOperation)
@ -999,8 +996,7 @@ func (s *ProcessService) processOrderItems(tx *gorm.DB, items []orderItemInfo, o
} }
// executeInventoryOperations 执行库存操作 // executeInventoryOperations 执行库存操作
func (s *ProcessService) executeInventoryOperations(tx *gorm.DB, inventoryOpMap map[inventoryKey]*inventoryOperation, func (s *ProcessService) executeInventoryOperations(tx *gorm.DB, inventoryOpMap map[inventoryKey]*inventoryOperation, inventoryLogs []models.InventoryLog, orderNo string, operator string, operatorID int64, now int64, changeType int8) error {
inventoryLogs []models.InventoryLog, orderNo string, operator string, operatorID int64, now int64, changeType int8) error {
for _, op := range inventoryOpMap { for _, op := range inventoryOpMap {
log, err := s.processInventoryOperation(tx, op.key, op.locationID, op.quantity, changeType, orderNo, operator, operatorID, now) log, err := s.processInventoryOperation(tx, op.key, op.locationID, op.quantity, changeType, orderNo, operator, operatorID, now)
@ -2667,8 +2663,7 @@ func (s *ProcessService) ChangeLocation(req systemReq.ChangeLocationRequest, ope
} }
// 创建波次 // 创建波次
func (s *ProcessService) createWaveHeader(tx *gorm.DB, waveNo string, direction int8, func (s *ProcessService) createWaveHeader(tx *gorm.DB, waveNo string, direction int8, warehouseID, relatedOrderID int64, creator string, creatorID int64) (*models.WaveHeader, error) {
warehouseID, relatedOrderID int64, creator string, creatorID int64) (*models.WaveHeader, error) {
now := time.Now().Unix() now := time.Now().Unix()
waveHeader := models.WaveHeader{ waveHeader := models.WaveHeader{
WaveNo: waveNo, WaveNo: waveNo,
@ -2718,8 +2713,7 @@ func (s *ProcessService) createWaveTaskDetails(tx *gorm.DB, waveTaskID int64, it
} }
// 创建波次任务和明细 // 创建波次任务和明细
func (s *ProcessService) createWaveTaskAndDetails(tx *gorm.DB, waveID int64, taskType int8, func (s *ProcessService) createWaveTaskAndDetails(tx *gorm.DB, waveID int64, taskType int8, items []WaveItemData, assignee string, assigneeID, carId, carCode, carCapacity int64) (*models.WaveTask, error) {
items []WaveItemData, assignee string, assigneeID, carId, carCode, carCapacity int64) (*models.WaveTask, error) {
now := time.Now().Unix() now := time.Now().Unix()
taskNo := utils.GenerateTaskNo() taskNo := utils.GenerateTaskNo()
taskTd := utils.GenerateTaskDetailNo() taskTd := utils.GenerateTaskDetailNo()
@ -2767,8 +2761,7 @@ func (s *ProcessService) createWaveTaskAndDetails(tx *gorm.DB, waveID int64, tas
} }
// createWaveTaskAndDetailsForOutbound 创建出库波次任务和明细(使用入库批次号) // createWaveTaskAndDetailsForOutbound 创建出库波次任务和明细(使用入库批次号)
func (s *ProcessService) createWaveTaskAndDetailsForOutbound(tx *gorm.DB, waveID int64, taskType int8, func (s *ProcessService) createWaveTaskAndDetailsForOutbound(tx *gorm.DB, waveID int64, taskType int8, items []WaveItemData, outboundItems []models.OutboundOrderItem, assignee string, assigneeID, carId, carCode, carCapacity int64) (*models.WaveTask, error) {
items []WaveItemData, outboundItems []models.OutboundOrderItem, assignee string, assigneeID, carId, carCode, carCapacity int64) (*models.WaveTask, error) {
now := time.Now().Unix() now := time.Now().Unix()
taskNo := utils.GenerateTaskNo() taskNo := utils.GenerateTaskNo()
@ -2864,8 +2857,7 @@ func (s *ProcessService) updateWaveTaskToPicking(tx *gorm.DB, waveTaskID int64)
} }
// 处理库存操作(使用原子操作和行级锁保证并发安全) // 处理库存操作(使用原子操作和行级锁保证并发安全)
func (s *ProcessService) processInventoryOperation(tx *gorm.DB, opKey inventoryKey, locationID int64, func (s *ProcessService) processInventoryOperation(tx *gorm.DB, opKey inventoryKey, locationID int64, quantity int64, changeType int8, orderNo string, operator string, operatorID int64, now int64) (*models.InventoryLog, error) {
quantity int64, changeType int8, orderNo string, operator string, operatorID int64, now int64) (*models.InventoryLog, error) {
if changeType == constant.InventoryChangeInbound { if changeType == constant.InventoryChangeInbound {
var inventory models.Inventory var inventory models.Inventory
@ -3499,8 +3491,7 @@ func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operat
} }
// processInventoryOperationForAdjustment 处理盘库调整的库存汇总操作 // processInventoryOperationForAdjustment 处理盘库调整的库存汇总操作
func (s *ProcessService) processInventoryOperationForAdjustment(tx *gorm.DB, opKey inventoryKey, locationID int64, func (s *ProcessService) processInventoryOperationForAdjustment(tx *gorm.DB, opKey inventoryKey, locationID int64, quantity int64, orderNo string, operator string, operatorID int64, now int64, remark string) (*models.InventoryLog, error) {
quantity int64, orderNo string, operator string, operatorID int64, now int64, remark string) (*models.InventoryLog, error) {
var inventory models.Inventory var inventory models.Inventory
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
@ -3598,8 +3589,7 @@ func (s *ProcessService) processInventoryOperationForAdjustment(tx *gorm.DB, opK
} }
// processInventoryDetailOperationForAdjustment 处理盘库调整的库存明细操作 // processInventoryDetailOperationForAdjustment 处理盘库调整的库存明细操作
func (s *ProcessService) processInventoryDetailOperationForAdjustment(tx *gorm.DB, opKey inventoryKey, locationID int64, func (s *ProcessService) processInventoryDetailOperationForAdjustment(tx *gorm.DB, opKey inventoryKey, locationID int64, quantity int64, now int64) error {
quantity int64, now int64) error {
var inventoryDetail models.InventoryDetail var inventoryDetail models.InventoryDetail
err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).

View File

@ -675,10 +675,10 @@ func (s *ProductService) GetProductInventory(req systemReq.GetProductInventoryRe
} }
var groupList []GroupStock var groupList []GroupStock
// 先根据商品的 ISBN 和品相,查询所有匹配的库存记录,再按仓库分组统计 // 先根据商品的 ISBN 和品相,查询所有匹配的库存记录,再按仓库分组统计(可用量 = 总量 - 锁定量)
databaseConn.Table("inventory"). databaseConn.Table("inventory").
Select(` Select(`
COALESCE(SUM(inventory.quantity), 0) as total_quantity COALESCE(SUM(inventory.quantity - inventory.locked_quantity), 0) as total_quantity
`). `).
Joins("LEFT JOIN product p ON inventory.product_id = p.id AND p.is_del = ?", 0). 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 = ?", Where("p.barcode = ? AND p.appearance = ? AND inventory.warehouse_id IS NOT NULL AND inventory.is_del = ?",
@ -686,7 +686,7 @@ func (s *ProductService) GetProductInventory(req systemReq.GetProductInventoryRe
Group("inventory.warehouse_id"). Group("inventory.warehouse_id").
Scan(&groupList) Scan(&groupList)
// 累加所有分组的数量 // 累加所有分组的可用数量
for _, group := range groupList { for _, group := range groupList {
totalQuantity += group.TotalQuantity totalQuantity += group.TotalQuantity
} }