feat: 商品追踪页面 + 出库提交库存校验
后端: - feat(backend): 重命名 Batches→Products 接口,新增库存状态(在售/已卖出)和买家信息 - feat(backend): 出库单创建/提交时校验仓库库存,不足则返回明确错误信息 - fix(backend): 出库创建 status 判断逻辑修复(空值默认 draft) 前端: - feat(client): 批次追踪改为商品追踪,新增状态列(在售/已卖出)和买家/时间列 - fix(client): 无批次号时显示"无批次"而非空 - refactor(client): BatchRecord → ProductTrackingRecord,repository 接口更新 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -64,6 +65,31 @@ func (h *StockOutHandler) Get(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"data": order})
|
||||
}
|
||||
|
||||
// checkInventory validates that warehouse has enough stock for each item.
|
||||
// warehouseID is the order's warehouse; items are the stock-out line items.
|
||||
func (h *StockOutHandler) checkInventory(shopID, warehouseID uint64, items []model.StockOutItem) error {
|
||||
for _, item := range items {
|
||||
var inv model.Inventory
|
||||
err := h.db.Where("shop_id = ? AND warehouse_id = ? AND product_id = ?",
|
||||
shopID, warehouseID, item.ProductID).First(&inv).Error
|
||||
if err != nil || inv.Quantity < item.Quantity {
|
||||
// Try to get product name for a clearer error message
|
||||
var p model.Product
|
||||
h.db.Where("id = ?", item.ProductID).First(&p)
|
||||
name := p.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("商品ID %d", item.ProductID)
|
||||
}
|
||||
have := 0.0
|
||||
if err == nil {
|
||||
have = inv.Quantity
|
||||
}
|
||||
return fmt.Errorf("库存不足:%s 当前库存 %.0f,需要 %.0f", name, have, item.Quantity)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create POST /api/v1/stock-out/orders
|
||||
func (h *StockOutHandler) Create(c *gin.Context) {
|
||||
shopID := middleware.GetShopID(c)
|
||||
@@ -77,7 +103,16 @@ func (h *StockOutHandler) Create(c *gin.Context) {
|
||||
|
||||
req.ShopID = shopID
|
||||
req.OperatorID = operatorID
|
||||
req.Status = "draft"
|
||||
|
||||
// 状态只允许 draft 或 pending;直接提交审核时校验库存
|
||||
if req.Status == "pending" {
|
||||
if err := h.checkInventory(shopID, req.WarehouseID, req.Items); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
req.Status = "draft"
|
||||
}
|
||||
|
||||
orderNo, err := h.stockSvc.GenerateOrderNo(shopID, "stock_out")
|
||||
if err != nil {
|
||||
@@ -104,13 +139,21 @@ func (h *StockOutHandler) Create(c *gin.Context) {
|
||||
// Submit PUT /api/v1/stock-out/orders/:id/submit
|
||||
func (h *StockOutHandler) Submit(c *gin.Context) {
|
||||
shopID := middleware.GetShopID(c)
|
||||
result := h.db.Model(&model.StockOutOrder{}).
|
||||
|
||||
// 加载订单及明细,校验库存后再改状态
|
||||
var order model.StockOutOrder
|
||||
if err := h.db.Preload("Items").
|
||||
Where("id = ? AND shop_id = ? AND status = 'draft'", c.Param("id"), shopID).
|
||||
Update("status", "pending")
|
||||
if result.RowsAffected == 0 {
|
||||
First(&order).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "order not found or not in draft status"})
|
||||
return
|
||||
}
|
||||
if err := h.checkInventory(shopID, order.WarehouseID, order.Items); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.Model(&order).Update("status", "pending")
|
||||
c.JSON(http.StatusOK, gin.H{"message": "submitted"})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user