提交版本
This commit is contained in:
parent
07d1dcbce2
commit
b02fe84356
@ -568,7 +568,7 @@ func (r *ProcessApi) UnlockSalesOrderInventory(c *gin.Context) {
|
||||
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 {
|
||||
utils.FailWithRequestLog(constant.LoggerChannelWork, "解锁销售订单库存异常", err, c, req)
|
||||
return
|
||||
|
||||
@ -135,8 +135,9 @@ type CancelSalesOrderRequest struct {
|
||||
|
||||
// UnlockSalesOrderInventoryRequest 解锁销售订单库存请求
|
||||
type UnlockSalesOrderInventoryRequest struct {
|
||||
AboutId int64 `form:"about_id"` // 租户ID(用于确定分库)
|
||||
AssociationOrderNo string `form:"association_order_no" binding:"required"` // 关联订单号(第三方订单号)
|
||||
AboutId int64 `form:"about_id"` // 租户ID(用于确定分库)
|
||||
AssociationOrderNo string `form:"association_order_no"` // 关联订单号(第三方订单号)
|
||||
AssociationOrderID int64 `form:"association_order_id"` // 关联订单ID(与association_order_no二选一)
|
||||
}
|
||||
|
||||
type CancelOutboundWaveRequest struct {
|
||||
|
||||
@ -1488,45 +1488,38 @@ func (s *ProcessService) CreateSalesOrderWithDetail(req systemReq.SalesOrderCrea
|
||||
|
||||
salesOrderID = salesOrder.ID
|
||||
|
||||
salesOrderItems := make([]models.SalesOrderItem, 0, len(req.Items))
|
||||
|
||||
// 先锁定库存,再用实际被锁定的商品ID创建明细
|
||||
var salesOrderItems []models.SalesOrderItem
|
||||
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)
|
||||
}
|
||||
|
||||
salesOrderItems = append(salesOrderItems, models.SalesOrderItem{
|
||||
SalesOrderID: salesOrderID,
|
||||
ProductID: itemReq.ProductID,
|
||||
Quantity: itemReq.Quantity,
|
||||
AllocatedQuantity: itemReq.Quantity,
|
||||
ShippedQuantity: 0,
|
||||
UnitPrice: itemReq.UnitPrice,
|
||||
Amount: amount,
|
||||
ReceiverName: req.ReceiverName,
|
||||
ReceiverPhone: req.ReceiverPhone,
|
||||
ReceiverAddress: req.ReceiverAddress,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
IsDel: 0,
|
||||
})
|
||||
for _, lr := range lockResults {
|
||||
amount := lr.Quantity * itemReq.UnitPrice
|
||||
salesOrderItems = append(salesOrderItems, models.SalesOrderItem{
|
||||
SalesOrderID: salesOrderID,
|
||||
ProductID: lr.ProductID,
|
||||
Quantity: lr.Quantity,
|
||||
AllocatedQuantity: lr.Quantity,
|
||||
ShippedQuantity: 0,
|
||||
UnitPrice: itemReq.UnitPrice,
|
||||
Amount: amount,
|
||||
ReceiverName: req.ReceiverName,
|
||||
ReceiverPhone: req.ReceiverPhone,
|
||||
ReceiverAddress: req.ReceiverAddress,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
IsDel: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Create(&salesOrderItems).Error; err != nil {
|
||||
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
|
||||
})
|
||||
|
||||
@ -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 {
|
||||
var inventories []models.Inventory
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("product_id = ? AND is_del = 0 AND locked_quantity > 0",
|
||||
productID).
|
||||
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无锁定库存", 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
|
||||
@ -3502,7 +3590,7 @@ func (s *ProcessService) CancelSalesOrder(orderID int64, operator string, operat
|
||||
|
||||
// CancelSalesOrder 取消销售订单并释放锁定库存
|
||||
// ... 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)
|
||||
if err != nil {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
for _, item := range orderItems {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -3770,22 +3850,28 @@ func (s *ProcessService) processInventoryDetailOperationForAdjustment(tx *gorm.D
|
||||
return nil
|
||||
}
|
||||
|
||||
type LockResult struct {
|
||||
ProductID int64
|
||||
Quantity int64
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
if err := tx.Model(&models.Product{}).
|
||||
Where("barcode = ? AND appearance = ? AND is_del = 0", product.Barcode, product.Appearance).
|
||||
Pluck("id", &matchingProductIDs).Error; err != nil {
|
||||
return fmt.Errorf("查询同品相商品失败: %v", err)
|
||||
return nil, fmt.Errorf("查询同品相商品失败: %v", err)
|
||||
}
|
||||
|
||||
if len(matchingProductIDs) == 0 {
|
||||
return fmt.Errorf("无匹配的商品记录")
|
||||
return nil, fmt.Errorf("无匹配的商品记录")
|
||||
}
|
||||
|
||||
var inventories []models.Inventory
|
||||
@ -3794,13 +3880,14 @@ func (s *ProcessService) lockInventoryByAppearance(tx *gorm.DB, warehouseID, pro
|
||||
warehouseID, matchingProductIDs).
|
||||
Order("product_id ASC, expiry_date ASC, created_at ASC").
|
||||
Find(&inventories).Error; err != nil {
|
||||
return fmt.Errorf("查询可用库存失败: %v", err)
|
||||
return nil, fmt.Errorf("查询可用库存失败: %v", err)
|
||||
}
|
||||
|
||||
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
|
||||
for i := range inventories {
|
||||
if remainingLock <= 0 {
|
||||
@ -3825,20 +3912,21 @@ func (s *ProcessService) lockInventoryByAppearance(tx *gorm.DB, warehouseID, pro
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("锁定库存失败: %v", result.Error)
|
||||
return nil, fmt.Errorf("锁定库存失败: %v", result.Error)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("库存已被其他事务修改,请重试")
|
||||
return nil, fmt.Errorf("库存已被其他事务修改,请重试")
|
||||
}
|
||||
|
||||
results = append(results, LockResult{ProductID: inventories[i].ProductID, Quantity: lockQty})
|
||||
remainingLock -= lockQty
|
||||
}
|
||||
|
||||
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