提交版本
This commit is contained in:
parent
07d1dcbce2
commit
b02fe84356
@ -568,7 +568,7 @@ func (r *ProcessApi) UnlockSalesOrderInventory(c *gin.Context) {
|
|||||||
aboutID = userInfo.AboutID
|
aboutID = userInfo.AboutID
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := processService.UnlockSalesOrderInventory(aboutID, req.AssociationOrderNo, userInfo.Username, userInfo.ID)
|
resp, err := processService.UnlockSalesOrderInventory(aboutID, req.AssociationOrderNo, req.AssociationOrderID, userInfo.Username, userInfo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.FailWithRequestLog(constant.LoggerChannelWork, "解锁销售订单库存异常", err, c, req)
|
utils.FailWithRequestLog(constant.LoggerChannelWork, "解锁销售订单库存异常", err, c, req)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -136,7 +136,8 @@ type CancelSalesOrderRequest struct {
|
|||||||
// UnlockSalesOrderInventoryRequest 解锁销售订单库存请求
|
// UnlockSalesOrderInventoryRequest 解锁销售订单库存请求
|
||||||
type UnlockSalesOrderInventoryRequest struct {
|
type UnlockSalesOrderInventoryRequest struct {
|
||||||
AboutId int64 `form:"about_id"` // 租户ID(用于确定分库)
|
AboutId int64 `form:"about_id"` // 租户ID(用于确定分库)
|
||||||
AssociationOrderNo string `form:"association_order_no" binding:"required"` // 关联订单号(第三方订单号)
|
AssociationOrderNo string `form:"association_order_no"` // 关联订单号(第三方订单号)
|
||||||
|
AssociationOrderID int64 `form:"association_order_id"` // 关联订单ID(与association_order_no二选一)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CancelOutboundWaveRequest struct {
|
type CancelOutboundWaveRequest struct {
|
||||||
|
|||||||
@ -1488,16 +1488,21 @@ func (s *ProcessService) CreateSalesOrderWithDetail(req systemReq.SalesOrderCrea
|
|||||||
|
|
||||||
salesOrderID = salesOrder.ID
|
salesOrderID = salesOrder.ID
|
||||||
|
|
||||||
salesOrderItems := make([]models.SalesOrderItem, 0, len(req.Items))
|
// 先锁定库存,再用实际被锁定的商品ID创建明细
|
||||||
|
var salesOrderItems []models.SalesOrderItem
|
||||||
for _, itemReq := range req.Items {
|
for _, itemReq := range req.Items {
|
||||||
amount := itemReq.Quantity * itemReq.UnitPrice
|
lockResults, err := s.lockInventoryByAppearance(tx, invWarehouseID, itemReq.ProductID, itemReq.Quantity, now)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("锁定库存失败[商品ID=%d]: %v", itemReq.ProductID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lr := range lockResults {
|
||||||
|
amount := lr.Quantity * itemReq.UnitPrice
|
||||||
salesOrderItems = append(salesOrderItems, models.SalesOrderItem{
|
salesOrderItems = append(salesOrderItems, models.SalesOrderItem{
|
||||||
SalesOrderID: salesOrderID,
|
SalesOrderID: salesOrderID,
|
||||||
ProductID: itemReq.ProductID,
|
ProductID: lr.ProductID,
|
||||||
Quantity: itemReq.Quantity,
|
Quantity: lr.Quantity,
|
||||||
AllocatedQuantity: itemReq.Quantity,
|
AllocatedQuantity: lr.Quantity,
|
||||||
ShippedQuantity: 0,
|
ShippedQuantity: 0,
|
||||||
UnitPrice: itemReq.UnitPrice,
|
UnitPrice: itemReq.UnitPrice,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
@ -1509,24 +1514,12 @@ func (s *ProcessService) CreateSalesOrderWithDetail(req systemReq.SalesOrderCrea
|
|||||||
IsDel: 0,
|
IsDel: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := tx.Create(&salesOrderItems).Error; err != nil {
|
if err := tx.Create(&salesOrderItems).Error; err != nil {
|
||||||
return fmt.Errorf("批量创建销售订单明细失败: %v", err)
|
return fmt.Errorf("批量创建销售订单明细失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 锁定库存
|
|
||||||
//for _, itemReq := range req.Items {
|
|
||||||
// if err := s.lockInventory(tx, invWarehouseID, itemReq.ProductID, itemReq.Quantity, now); err != nil {
|
|
||||||
// return fmt.Errorf("锁定库存失败[商品ID=%d]: %v", itemReq.ProductID, err)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
// 锁定库存
|
|
||||||
for _, itemReq := range req.Items {
|
|
||||||
if err := s.lockInventoryByAppearance(tx, invWarehouseID, itemReq.ProductID, itemReq.Quantity, now); err != nil {
|
|
||||||
return fmt.Errorf("锁定库存失败[商品ID=%d]: %v", itemReq.ProductID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -3323,15 +3316,110 @@ func (s *ProcessService) lockInventory(tx *gorm.DB, warehouseID, productID, quan
|
|||||||
func (s *ProcessService) unlockInventory(tx *gorm.DB, warehouseID, productID, quantity int64, now int64) error {
|
func (s *ProcessService) unlockInventory(tx *gorm.DB, warehouseID, productID, quantity int64, now int64) error {
|
||||||
var inventories []models.Inventory
|
var inventories []models.Inventory
|
||||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
Where("product_id = ? AND is_del = 0 AND locked_quantity > 0",
|
Where("warehouse_id = ? AND product_id = ? AND is_del = 0 AND locked_quantity > 0",
|
||||||
productID).
|
warehouseID, productID).
|
||||||
Order("created_at DESC").
|
Order("created_at DESC").
|
||||||
Find(&inventories).Error; err != nil {
|
Find(&inventories).Error; err != nil {
|
||||||
return fmt.Errorf("查询锁定库存失败: %v", err)
|
return fmt.Errorf("查询锁定库存失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inventories) == 0 {
|
if len(inventories) == 0 {
|
||||||
return fmt.Errorf("商品ID=%d无锁定库存", productID)
|
return fmt.Errorf("商品ID=%d在仓库ID=%d无锁定库存", productID, warehouseID)
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingUnlock := quantity
|
||||||
|
for i := range inventories {
|
||||||
|
if remainingUnlock <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockQty := inventories[i].LockedQuantity
|
||||||
|
if unlockQty > remainingUnlock {
|
||||||
|
unlockQty = remainingUnlock
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tx.Model(&inventories[i]).
|
||||||
|
Where("locked_quantity >= ?", unlockQty).
|
||||||
|
UpdateColumns(map[string]interface{}{
|
||||||
|
"locked_quantity": gorm.Expr("locked_quantity - ?", unlockQty),
|
||||||
|
"updated_at": now,
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("解锁库存失败: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("锁定库存已被其他事务修改,请重试")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步解锁 inventory_detail 表
|
||||||
|
var inventoryDetails []models.InventoryDetail
|
||||||
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Where("warehouse_id = ? AND product_id = ? AND is_del = 0 AND locked_quantity > 0",
|
||||||
|
warehouseID, productID).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&inventoryDetails).Error; err != nil {
|
||||||
|
return fmt.Errorf("查询锁定库存明细失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
detailRemainingUnlock := unlockQty
|
||||||
|
for j := range inventoryDetails {
|
||||||
|
if detailRemainingUnlock <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
detailUnlockQty := inventoryDetails[j].LockedQuantity
|
||||||
|
if detailUnlockQty > detailRemainingUnlock {
|
||||||
|
detailUnlockQty = detailRemainingUnlock
|
||||||
|
}
|
||||||
|
|
||||||
|
detailResult := tx.Model(&inventoryDetails[j]).
|
||||||
|
Where("locked_quantity >= ?", detailUnlockQty).
|
||||||
|
UpdateColumns(map[string]interface{}{
|
||||||
|
"locked_quantity": gorm.Expr("locked_quantity - ?", detailUnlockQty),
|
||||||
|
"updated_at": now,
|
||||||
|
})
|
||||||
|
|
||||||
|
if detailResult.Error != nil {
|
||||||
|
return fmt.Errorf("解锁库存明细失败: %v", detailResult.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if detailResult.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("库存明细已被其他事务修改,请重试")
|
||||||
|
}
|
||||||
|
|
||||||
|
detailRemainingUnlock -= detailUnlockQty
|
||||||
|
}
|
||||||
|
|
||||||
|
if detailRemainingUnlock > 0 {
|
||||||
|
return fmt.Errorf("库存明细锁定数量不足,还需解锁:%d", detailRemainingUnlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingUnlock -= unlockQty
|
||||||
|
}
|
||||||
|
|
||||||
|
if remainingUnlock > 0 {
|
||||||
|
return fmt.Errorf("锁定库存不足,还需解锁:%d", remainingUnlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockInventoryNoDetail 仅解锁inventory表,不操作inventory_detail
|
||||||
|
// 用于lock时也没有操作inventory_detail的场景
|
||||||
|
func (s *ProcessService) unlockInventoryNoDetail(tx *gorm.DB, warehouseID, productID, quantity int64, now int64) error {
|
||||||
|
var inventories []models.Inventory
|
||||||
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Where("warehouse_id = ? AND product_id = ? AND is_del = 0 AND locked_quantity > 0",
|
||||||
|
warehouseID, productID).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&inventories).Error; err != nil {
|
||||||
|
return fmt.Errorf("查询锁定库存失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inventories) == 0 {
|
||||||
|
return fmt.Errorf("商品ID=%d在仓库ID=%d无锁定库存", productID, warehouseID)
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingUnlock := quantity
|
remainingUnlock := quantity
|
||||||
@ -3502,7 +3590,7 @@ func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operat
|
|||||||
|
|
||||||
// CancelSalesOrder 取消销售订单并释放锁定库存
|
// CancelSalesOrder 取消销售订单并释放锁定库存
|
||||||
// ... existing code ...
|
// ... existing code ...
|
||||||
func (s *ProcessService) UnlockSalesOrderInventory(aboutID int64, associationOrderNo string, operator string, operatorID int64) (*systemRes.UnlockInventoryResponse, error) {
|
func (s *ProcessService) UnlockSalesOrderInventory(aboutID int64, associationOrderNo string, associationOrderID int64, operator string, operatorID int64) (*systemRes.UnlockInventoryResponse, error) {
|
||||||
databaseConn, err := database.GetTenantDB(aboutID)
|
databaseConn, err := database.GetTenantDB(aboutID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("获取数据库连接失败: %v", err)
|
return nil, fmt.Errorf("获取数据库连接失败: %v", err)
|
||||||
@ -3513,19 +3601,11 @@ func (s *ProcessService) UnlockSalesOrderInventory(aboutID int64, associationOrd
|
|||||||
|
|
||||||
err = executeInTransactionWithDB(databaseConn, func(tx *gorm.DB) error {
|
err = executeInTransactionWithDB(databaseConn, func(tx *gorm.DB) error {
|
||||||
var salesOrder models.SalesOrder
|
var salesOrder models.SalesOrder
|
||||||
if err := tx.Where("association_order_no = ? AND is_del = 0", associationOrderNo).First(&salesOrder).Error; err != nil {
|
if err := tx.Where("association_order_id = ? AND association_order_no = ? AND is_del = 0", associationOrderID, associationOrderNo).First(&salesOrder).Error; err != nil {
|
||||||
return fmt.Errorf("销售订单不存在: %v", err)
|
return fmt.Errorf("销售订单不存在: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if salesOrder.Status == constant.SalesStatusShipped {
|
// 状态检查已移除:任何状态均可解锁
|
||||||
return fmt.Errorf("订单已发货,无法解锁库存")
|
|
||||||
}
|
|
||||||
if salesOrder.Status == constant.SalesStatusCancelled {
|
|
||||||
return fmt.Errorf("订单已取消,无法解锁库存")
|
|
||||||
}
|
|
||||||
if salesOrder.Status != constant.SalesStatusAllocated && salesOrder.Status != constant.SalesStatusPicking {
|
|
||||||
return fmt.Errorf("订单状态不允许解锁库存,当前状态: %s", getSalesStatusText(salesOrder.Status))
|
|
||||||
}
|
|
||||||
|
|
||||||
var orderItems []models.SalesOrderItem
|
var orderItems []models.SalesOrderItem
|
||||||
if err := tx.Where("sales_order_id = ? AND is_del = 0", salesOrder.ID).Find(&orderItems).Error; err != nil {
|
if err := tx.Where("sales_order_id = ? AND is_del = 0", salesOrder.ID).Find(&orderItems).Error; err != nil {
|
||||||
@ -3556,7 +3636,7 @@ func (s *ProcessService) UnlockSalesOrderInventory(aboutID int64, associationOrd
|
|||||||
var responseItems []systemRes.UnlockInventoryItemResponse
|
var responseItems []systemRes.UnlockInventoryItemResponse
|
||||||
for _, item := range orderItems {
|
for _, item := range orderItems {
|
||||||
if item.AllocatedQuantity > 0 {
|
if item.AllocatedQuantity > 0 {
|
||||||
if err := s.unlockInventory(tx, salesOrder.WarehouseID, item.ProductID, item.AllocatedQuantity, now); err != nil {
|
if err := s.unlockInventoryNoDetail(tx, salesOrder.WarehouseID, item.ProductID, item.AllocatedQuantity, now); err != nil {
|
||||||
return fmt.Errorf("解锁库存失败[商品ID=%d]: %v", item.ProductID, err)
|
return fmt.Errorf("解锁库存失败[商品ID=%d]: %v", item.ProductID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3770,22 +3850,28 @@ func (s *ProcessService) processInventoryDetailOperationForAdjustment(tx *gorm.D
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LockResult struct {
|
||||||
|
ProductID int64
|
||||||
|
Quantity int64
|
||||||
|
}
|
||||||
|
|
||||||
// lockInventoryByAppearance 按品相+ISBN匹配所有商品的库存进行锁定
|
// lockInventoryByAppearance 按品相+ISBN匹配所有商品的库存进行锁定
|
||||||
func (s *ProcessService) lockInventoryByAppearance(tx *gorm.DB, warehouseID, productID, quantity int64, now int64) error {
|
// 返回实际被锁定的商品ID及对应数量
|
||||||
|
func (s *ProcessService) lockInventoryByAppearance(tx *gorm.DB, warehouseID, productID, quantity int64, now int64) ([]LockResult, error) {
|
||||||
var product models.Product
|
var product models.Product
|
||||||
if err := tx.Where("id = ? AND is_del = 0", productID).First(&product).Error; err != nil {
|
if err := tx.Where("id = ? AND is_del = 0", productID).First(&product).Error; err != nil {
|
||||||
return fmt.Errorf("查询商品信息失败: %v", err)
|
return nil, fmt.Errorf("查询商品信息失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchingProductIDs []int64
|
var matchingProductIDs []int64
|
||||||
if err := tx.Model(&models.Product{}).
|
if err := tx.Model(&models.Product{}).
|
||||||
Where("barcode = ? AND appearance = ? AND is_del = 0", product.Barcode, product.Appearance).
|
Where("barcode = ? AND appearance = ? AND is_del = 0", product.Barcode, product.Appearance).
|
||||||
Pluck("id", &matchingProductIDs).Error; err != nil {
|
Pluck("id", &matchingProductIDs).Error; err != nil {
|
||||||
return fmt.Errorf("查询同品相商品失败: %v", err)
|
return nil, fmt.Errorf("查询同品相商品失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(matchingProductIDs) == 0 {
|
if len(matchingProductIDs) == 0 {
|
||||||
return fmt.Errorf("无匹配的商品记录")
|
return nil, fmt.Errorf("无匹配的商品记录")
|
||||||
}
|
}
|
||||||
|
|
||||||
var inventories []models.Inventory
|
var inventories []models.Inventory
|
||||||
@ -3794,13 +3880,14 @@ func (s *ProcessService) lockInventoryByAppearance(tx *gorm.DB, warehouseID, pro
|
|||||||
warehouseID, matchingProductIDs).
|
warehouseID, matchingProductIDs).
|
||||||
Order("product_id ASC, expiry_date ASC, created_at ASC").
|
Order("product_id ASC, expiry_date ASC, created_at ASC").
|
||||||
Find(&inventories).Error; err != nil {
|
Find(&inventories).Error; err != nil {
|
||||||
return fmt.Errorf("查询可用库存失败: %v", err)
|
return nil, fmt.Errorf("查询可用库存失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inventories) == 0 {
|
if len(inventories) == 0 {
|
||||||
return fmt.Errorf("商品(barcode=%s,appearance=%d)在仓库ID=%d中无可用库存", product.Barcode, product.Appearance, warehouseID)
|
return nil, fmt.Errorf("商品(barcode=%s,appearance=%d)在仓库ID=%d中无可用库存", product.Barcode, product.Appearance, warehouseID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var results []LockResult
|
||||||
remainingLock := quantity
|
remainingLock := quantity
|
||||||
for i := range inventories {
|
for i := range inventories {
|
||||||
if remainingLock <= 0 {
|
if remainingLock <= 0 {
|
||||||
@ -3825,20 +3912,21 @@ func (s *ProcessService) lockInventoryByAppearance(tx *gorm.DB, warehouseID, pro
|
|||||||
})
|
})
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return fmt.Errorf("锁定库存失败: %v", result.Error)
|
return nil, fmt.Errorf("锁定库存失败: %v", result.Error)
|
||||||
}
|
}
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return fmt.Errorf("库存已被其他事务修改,请重试")
|
return nil, fmt.Errorf("库存已被其他事务修改,请重试")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results = append(results, LockResult{ProductID: inventories[i].ProductID, Quantity: lockQty})
|
||||||
remainingLock -= lockQty
|
remainingLock -= lockQty
|
||||||
}
|
}
|
||||||
|
|
||||||
if remainingLock > 0 {
|
if remainingLock > 0 {
|
||||||
return fmt.Errorf("可用库存不足,还需锁定:%d", remainingLock)
|
return nil, fmt.Errorf("可用库存不足,还需锁定:%d", remainingLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行事务(使用指定数据库)
|
// 执行事务(使用指定数据库)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user