From 1b4ca0c675c88d270516567ac3942ccbba869f33 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sat, 23 May 2026 14:16:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(test):=20=E4=BF=AE=E5=A4=8D=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=87=E4=BB=B6=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=92=8C=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE=E5=BA=93=20schema?= =?UTF-8?q?=20=E8=BF=87=E6=9C=9F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 stock_in/out 测试中 WarehouseID/ProductID 指针误用(StockOrder 用 uint64,Inventory 用 *uint64) - 更新 testutil/setup.go:inventories 表加入所有批次字段,stock_in_items 加 production_date,新增 finance_records 表 - 修正 inventory handler 中 DATE_FORMAT(MySQL 专属)→ DATE()(跨 DB 兼容) - 更新断言:order_no 前缀改为 RK,库存总量用 SUM 替代 First - 库存 List 新增 in_stock=1 过滤器 Co-Authored-By: Claude Sonnet 4.6 --- backend/internal/handler/inventory.go | 8 +++-- backend/internal/handler/inventory_test.go | 18 +++++----- backend/internal/handler/stock_out_test.go | 20 +++++------ backend/internal/service/stock_test.go | 24 +++++++------ backend/testutil/setup.go | 39 ++++++++++++++++++++-- 5 files changed, 74 insertions(+), 35 deletions(-) diff --git a/backend/internal/handler/inventory.go b/backend/internal/handler/inventory.go index b0ec94a..92f29b1 100644 --- a/backend/internal/handler/inventory.go +++ b/backend/internal/handler/inventory.go @@ -61,6 +61,7 @@ func (h *InventoryHandler) List(c *gin.Context) { keyword := c.Query("keyword") warehouseIDStr := c.Query("warehouse_id") + inStock := c.Query("in_stock") if keyword != "" { baseWhere += " AND (COALESCE(NULLIF(p.name,''), inv.product_name) LIKE ? OR COALESCE(NULLIF(p.code,''), inv.product_code) LIKE ?)" @@ -71,6 +72,9 @@ func (h *InventoryHandler) List(c *gin.Context) { baseWhere += " AND inv.warehouse_id = ?" args = append(args, warehouseIDStr) } + if inStock == "1" { + baseWhere += " AND inv.quantity > 0" + } // Count query countSQL := ` @@ -96,13 +100,13 @@ func (h *InventoryHandler) List(c *gin.Context) { COALESCE(NULLIF(p.unit,''), inv.unit, '') AS unit, COALESCE(NULLIF(w.name,''), inv.warehouse_name, '') AS warehouse_name, COALESCE(sii.unit_price, inv.unit_price) AS unit_price, - COALESCE(DATE_FORMAT(sii.production_date,'%Y-%m-%d'), DATE_FORMAT(inv.production_date,'%Y-%m-%d')) AS production_date, + COALESCE(DATE(sii.production_date), DATE(inv.production_date)) AS production_date, COALESCE(NULLIF(sii.batch_no,''), inv.batch_no, '') AS batch_no, inv.supplier_name, inv.remark, COALESCE(p.brand, '') AS brand, p.min_stock, - DATE_FORMAT(inv.created_at, '%Y-%m-%dT%H:%i:%sZ') AS created_at + inv.created_at FROM inventories inv LEFT JOIN stock_in_items sii ON sii.id = inv.stock_in_item_id LEFT JOIN products p ON p.id = inv.product_id diff --git a/backend/internal/handler/inventory_test.go b/backend/internal/handler/inventory_test.go index 8b27784..355d6a2 100644 --- a/backend/internal/handler/inventory_test.go +++ b/backend/internal/handler/inventory_test.go @@ -25,8 +25,8 @@ func TestInventoryHandler_List(t *testing.T) { // 直接插入库存记录 inv := model.Inventory{ ShopID: shop.ID, - WarehouseID: warehouse.ID, - ProductID: product.ID, + WarehouseID: &warehouse.ID, + ProductID: &product.ID, Quantity: 100, } require.NoError(t, db.Create(&inv).Error) @@ -49,8 +49,8 @@ func TestInventoryHandler_List_FilterByWarehouse(t *testing.T) { r := setupProtectedRouter(db) // 两个仓库各有一个库存 - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse1.ID, ProductID: product1.ID, Quantity: 10}) - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse2.ID, ProductID: product2.ID, Quantity: 20}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse1.ID, ProductID: &product1.ID, Quantity: 10}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse2.ID, ProductID: &product2.ID, Quantity: 20}) // 按仓库过滤 w := makeRequest(r, "GET", fmt.Sprintf("/api/v1/inventory?warehouse_id=%d", warehouse1.ID), token, nil) @@ -69,8 +69,8 @@ func TestInventoryHandler_List_InStockOnly(t *testing.T) { token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse.ID, ProductID: product1.ID, Quantity: 10}) - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse.ID, ProductID: product2.ID, Quantity: 0}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse.ID, ProductID: &product1.ID, Quantity: 10}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse.ID, ProductID: &product2.ID, Quantity: 0}) // 只显示有库存的 w := makeRequest(r, "GET", "/api/v1/inventory?in_stock=1", token, nil) @@ -121,8 +121,8 @@ func TestInventoryHandler_CreateCheck(t *testing.T) { // 先创建库存 db.Create(&model.Inventory{ ShopID: shop.ID, - WarehouseID: warehouse.ID, - ProductID: product.ID, + WarehouseID: &warehouse.ID, + ProductID: &product.ID, Quantity: 50, }) @@ -206,7 +206,7 @@ func TestInventoryHandler_List_HotelIsolation(t *testing.T) { r := setupProtectedRouter(db) // 酒店 A 有库存 - db.Create(&model.Inventory{ShopID: shopA.ID, WarehouseID: warehouseA.ID, ProductID: productA.ID, Quantity: 100}) + db.Create(&model.Inventory{ShopID: shopA.ID, WarehouseID: &warehouseA.ID, ProductID: &productA.ID, Quantity: 100}) // 酒店 A 能看到自己的库存 w := makeRequest(r, "GET", "/api/v1/inventory", tokenA, nil) diff --git a/backend/internal/handler/stock_out_test.go b/backend/internal/handler/stock_out_test.go index 7d9d1b4..9a7ec93 100644 --- a/backend/internal/handler/stock_out_test.go +++ b/backend/internal/handler/stock_out_test.go @@ -25,8 +25,8 @@ func TestStockOutHandler_FullFlow(t *testing.T) { // 先建立库存 db.Create(&model.Inventory{ ShopID: shop.ID, - WarehouseID: warehouse.ID, - ProductID: product.ID, + WarehouseID: &warehouse.ID, + ProductID: &product.ID, Quantity: 100, }) @@ -83,8 +83,8 @@ func TestStockOutHandler_InsufficientStock(t *testing.T) { // 库存只有 5 db.Create(&model.Inventory{ ShopID: shop.ID, - WarehouseID: warehouse.ID, - ProductID: product.ID, + WarehouseID: &warehouse.ID, + ProductID: &product.ID, Quantity: 5, }) @@ -114,7 +114,7 @@ func TestStockOutHandler_List(t *testing.T) { r := setupProtectedRouter(db) // 先建立库存 - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse.ID, ProductID: product.ID, Quantity: 100}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse.ID, ProductID: &product.ID, Quantity: 100}) // 创建 2 个出库单 for i := 0; i < 2; i++ { @@ -142,7 +142,7 @@ func TestStockOutHandler_Reject(t *testing.T) { token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse.ID, ProductID: product.ID, Quantity: 100}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse.ID, ProductID: &product.ID, Quantity: 100}) w := makeRequest(r, "POST", "/api/v1/stock-out/orders", token, map[string]interface{}{ "warehouse_id": warehouse.ID, @@ -206,8 +206,8 @@ func TestStockOutHandler_TenantIsolation(t *testing.T) { // 建立门店 A 的库存 db.Create(&model.Inventory{ ShopID: shopA.ID, - WarehouseID: warehouseA.ID, - ProductID: productA.ID, + WarehouseID: &warehouseA.ID, + ProductID: &productA.ID, Quantity: 50, }) @@ -259,7 +259,7 @@ func TestStockOutHandler_Approve_NotPending(t *testing.T) { token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse.ID, ProductID: product.ID, Quantity: 50}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse.ID, ProductID: &product.ID, Quantity: 50}) // 创建但不提交 w := makeRequest(r, "POST", "/api/v1/stock-out/orders", token, map[string]interface{}{ @@ -285,7 +285,7 @@ func TestStockOutHandler_InventoryLog_OnApprove(t *testing.T) { token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) - db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: warehouse.ID, ProductID: product.ID, Quantity: 100}) + db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &warehouse.ID, ProductID: &product.ID, Quantity: 100}) // 创建、提交、审核 w := makeRequest(r, "POST", "/api/v1/stock-out/orders", token, map[string]interface{}{ diff --git a/backend/internal/service/stock_test.go b/backend/internal/service/stock_test.go index 29c0dc9..6acb019 100644 --- a/backend/internal/service/stock_test.go +++ b/backend/internal/service/stock_test.go @@ -98,8 +98,8 @@ func TestStockService_ApproveStockOut_Success(t *testing.T) { // 先入库 inv := model.Inventory{ ShopID: shop.ID, - WarehouseID: warehouse.ID, - ProductID: product.ID, + WarehouseID: &warehouse.ID, + ProductID: &product.ID, Quantity: 20, } require.NoError(t, db.Create(&inv).Error) @@ -149,8 +149,8 @@ func TestStockService_ApproveStockOut_InsufficientStock(t *testing.T) { // 库存只有 3 inv := model.Inventory{ ShopID: shop.ID, - WarehouseID: warehouse.ID, - ProductID: product.ID, + WarehouseID: &warehouse.ID, + ProductID: &product.ID, Quantity: 3, } require.NoError(t, db.Create(&inv).Error) @@ -207,7 +207,7 @@ func TestStockService_ApproveStockOut_ProductNotInInventory(t *testing.T) { svc := NewStockService(db) err := svc.ApproveStockOut(shop.ID, order.ID, user.ID) assert.Error(t, err) - assert.Contains(t, err.Error(), "not in inventory") + assert.ErrorIs(t, err, ErrInsufficientStock) } func TestStockService_GenerateOrderNo(t *testing.T) { @@ -219,7 +219,7 @@ func TestStockService_GenerateOrderNo(t *testing.T) { require.NoError(t, err) assert.NotEmpty(t, no) // 单号格式: prefix + date + 6位序号 - assert.Contains(t, no, "st") + assert.Contains(t, no, "RK") // 第二次生成序号应递增 no2, err := svc.GenerateOrderNo(shop.ID, "stock_in") @@ -288,9 +288,11 @@ func TestStockService_MultipleStockIn_AccumulatesInventory(t *testing.T) { require.NoError(t, db.Create(&order2).Error) require.NoError(t, svc.ApproveStockIn(shop.ID, order2.ID, user.ID)) - // 总库存应该是 15 - var inv model.Inventory - db.Where("shop_id = ? AND warehouse_id = ? AND product_id = ?", - shop.ID, warehouse.ID, product.ID).First(&inv) - assert.Equal(t, float64(15), inv.Quantity) + // 总库存应该是 15(两个批次之和) + var totalQty float64 + db.Model(&model.Inventory{}). + Where("shop_id = ? AND warehouse_id = ? AND product_id = ? AND deleted_at IS NULL", + shop.ID, &warehouse.ID, &product.ID). + Select("COALESCE(SUM(quantity), 0)").Scan(&totalQty) + assert.Equal(t, float64(15), totalQty) } diff --git a/backend/testutil/setup.go b/backend/testutil/setup.go index f2629cb..8c25d47 100644 --- a/backend/testutil/setup.go +++ b/backend/testutil/setup.go @@ -183,6 +183,7 @@ func SetupTestDB() *gorm.DB { unit_price REAL DEFAULT 0, total_price REAL DEFAULT 0, batch_no TEXT, + production_date DATETIME, custom_fields TEXT, remark TEXT )`, @@ -223,11 +224,25 @@ func SetupTestDB() *gorm.DB { `CREATE TABLE IF NOT EXISTS inventories ( id INTEGER PRIMARY KEY AUTOINCREMENT, shop_id INTEGER NOT NULL, - warehouse_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, + warehouse_id INTEGER, + product_id INTEGER, + stock_in_item_id INTEGER, + inventory_check_id INTEGER, quantity REAL DEFAULT 0, + product_code TEXT DEFAULT '', + product_name TEXT DEFAULT '', + series TEXT DEFAULT '', + spec TEXT DEFAULT '', + unit TEXT DEFAULT '', + warehouse_name TEXT DEFAULT '', + unit_price REAL, + production_date DATETIME, + batch_no TEXT DEFAULT '', + supplier_name TEXT DEFAULT '', + remark TEXT DEFAULT '', + created_at DATETIME, updated_at DATETIME, - UNIQUE(shop_id, warehouse_id, product_id) + deleted_at DATETIME )`, `CREATE TABLE IF NOT EXISTS inventory_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -269,6 +284,24 @@ func SetupTestDB() *gorm.DB { actual_qty REAL, remark TEXT )`, + `CREATE TABLE IF NOT EXISTS finance_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at DATETIME, + updated_at DATETIME, + deleted_at DATETIME, + shop_id INTEGER NOT NULL, + partner_id INTEGER, + type TEXT, + amount REAL DEFAULT 0, + balance REAL DEFAULT 0, + status TEXT DEFAULT 'open', + ref_type TEXT, + ref_id INTEGER, + operator_id INTEGER, + record_date DATETIME, + remark TEXT, + custom_fields TEXT + )`, `CREATE TABLE IF NOT EXISTS number_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, shop_id INTEGER NOT NULL,