# 入库/出库单 Bug 报告 ## BUG-001 **严重程度**:中 **问题描述**: 创建入库单(`POST /api/v1/stock-in/orders`)和出库单(`POST /api/v1/stock-out/orders`)时,缺少对 `warehouse_id` 的 `binding:"required"` 验证。当客户端不传 `warehouse_id` 或传入 `0`,后端会静默接受并创建一条 `warehouse_id=0` 的无效单据,而不是返回 400 Bad Request。 **复现步骤**: 1. 调用 `POST /api/v1/stock-in/orders`,请求体中省略 `warehouse_id` 字段 2. 期望返回 400,实际返回 201,并创建了一条 `warehouse_id=0` 的单据 等效对出库单同样适用。 **失败的测试用例**: ```go func TestStockInHandler_Create_MissingWarehouse(t *testing.T) { // 缺少 warehouse_id,应该返回 400 w := makeRequest(r, "POST", "/api/v1/stock-in/orders", token, map[string]interface{}{ "order_date": time.Now().Format(time.RFC3339), "items": []map[string]interface{}{}, }) assert.Equal(t, http.StatusBadRequest, w.Code) // 实际返回 201 } ``` **根因分析**: - `backend/internal/model/stock.go` 的 `StockInOrder` 和 `StockOutOrder` 结构体中,`WarehouseID` 字段缺少 `binding:"required"` 标签 - 当前定义:`WarehouseID uint64 \`gorm:"not null" json:"warehouse_id"\`` - 缺少:`binding:"required"` **修复建议**: 在 `StockInOrder` 和 `StockOutOrder` 的 `WarehouseID` 字段上添加 `binding:"required"` 标签: ```go WarehouseID uint64 `gorm:"not null" json:"warehouse_id" binding:"required"` ``` 注意:Go 的 `binding:"required"` 对 `uint64` 类型的判断是零值(即 0)视为未填写,因此可正确拦截缺失或为 0 的情况。 --- ## BUG-002 **严重程度**:低 **问题描述**: `WarehouseHandler.Delete` 在资源不存在时返回 200 而不是 404。当用户删除一个不存在的仓库 ID 时,业务逻辑没有检查 `RowsAffected`,始终返回 200 OK。 **复现步骤**: 1. 调用 `DELETE /api/v1/warehouses/99999`(不存在的 ID) 2. 期望返回 404,实际返回 200 **根因分析**: `backend/internal/handler/warehouse.go` 的 `Delete` 方法未检查 `result.RowsAffected`,与 `ProductHandler.Delete` 的实现不一致。 **修复建议**: 参考 `ProductHandler.Delete` 的实现,添加 `RowsAffected == 0` 检查: ```go func (h *WarehouseHandler) Delete(c *gin.Context) { shopID := middleware.GetShopID(c) now := timeNow() result := h.db.Model(&model.Warehouse{}). Where("id = ? AND shop_id = ? AND deleted_at IS NULL", c.Param("id"), shopID). Update("deleted_at", now) if result.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "deleted"}) } ```