package handler import ( "fmt" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/wangjia/jiu/backend/internal/model" "github.com/wangjia/jiu/backend/testutil" ) func TestInventoryHandler_List(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV001") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse") product := testutil.CreateTestProduct(db, shop.ID, "Beer") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 直接插入库存记录 inv := model.Inventory{ ShopID: shop.ID, WarehouseID: &warehouse.ID, ProductID: &product.ID, Quantity: 100, } require.NoError(t, db.Create(&inv).Error) w := makeRequest(r, "GET", "/api/v1/inventory", token, nil) assert.Equal(t, http.StatusOK, w.Code) resp := parseResponse(w) assert.Equal(t, float64(1), resp["total"].(float64)) } func TestInventoryHandler_List_FilterByWarehouse(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV002") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") warehouse1 := testutil.CreateTestWarehouse(db, shop.ID, "W1") warehouse2 := testutil.CreateTestWarehouse(db, shop.ID, "W2") product1 := testutil.CreateTestProduct(db, shop.ID, "Beer1") product2 := testutil.CreateTestProduct(db, shop.ID, "Beer2") token := getAuthToken(user.ID, shop.ID, "admin") 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}) // 按仓库过滤 w := makeRequest(r, "GET", fmt.Sprintf("/api/v1/inventory?warehouse_id=%d", warehouse1.ID), token, nil) assert.Equal(t, http.StatusOK, w.Code) resp := parseResponse(w) assert.Equal(t, float64(1), resp["total"].(float64)) } func TestInventoryHandler_List_InStockOnly(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV003") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse") product1 := testutil.CreateTestProduct(db, shop.ID, "InStock") product2 := testutil.CreateTestProduct(db, shop.ID, "OutOfStock") 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}) // 只显示有库存的 w := makeRequest(r, "GET", "/api/v1/inventory?in_stock=1", token, nil) assert.Equal(t, http.StatusOK, w.Code) resp := parseResponse(w) assert.Equal(t, float64(1), resp["total"].(float64)) } func TestInventoryHandler_Logs(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV004") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse") product := testutil.CreateTestProduct(db, shop.ID, "Wine") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 创建库存流水 opID := user.ID db.Create(&model.InventoryLog{ ShopID: shop.ID, WarehouseID: warehouse.ID, ProductID: product.ID, Direction: "in", Quantity: 10, QtyBefore: 0, QtyAfter: 10, RefType: "stock_in", RefID: 1, OperatorID: &opID, }) w := makeRequest(r, "GET", "/api/v1/inventory/logs", token, nil) assert.Equal(t, http.StatusOK, w.Code) resp := parseResponse(w) assert.Equal(t, float64(1), resp["total"].(float64)) } func TestInventoryHandler_CreateCheck(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV005") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse") product := testutil.CreateTestProduct(db, shop.ID, "Whiskey") 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, }) // 创建盘点单 w := makeRequest(r, "POST", "/api/v1/inventory/checks", token, map[string]interface{}{ "check_no": "CHK001", "warehouse_id": warehouse.ID, "check_date": time.Now().Format(time.RFC3339), "items": []map[string]interface{}{ { "product_id": product.ID, "actual_qty": 48.0, }, }, }) require.Equal(t, http.StatusCreated, w.Code) checkID := extractID(w) assert.NotZero(t, checkID) data := parseResponse(w)["data"].(map[string]interface{}) assert.Equal(t, "draft", data["status"]) // 检查 system_qty 是否自动填入 items := data["items"].([]interface{}) require.Len(t, items, 1) item := items[0].(map[string]interface{}) assert.Equal(t, float64(50), item["system_qty"]) } func TestInventoryHandler_GetCheck(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV006") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse") product := testutil.CreateTestProduct(db, shop.ID, "Vodka") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 创建盘点单 w := makeRequest(r, "POST", "/api/v1/inventory/checks", token, map[string]interface{}{ "check_no": "CHK002", "warehouse_id": warehouse.ID, "check_date": time.Now().Format(time.RFC3339), "items": []map[string]interface{}{ {"product_id": product.ID, "actual_qty": 10.0}, }, }) checkID := extractID(w) // 获取盘点单 w = makeRequest(r, "GET", fmt.Sprintf("/api/v1/inventory/checks/%d", checkID), token, nil) assert.Equal(t, http.StatusOK, w.Code) data := parseResponse(w)["data"].(map[string]interface{}) assert.Equal(t, "CHK002", data["check_no"]) } func TestInventoryHandler_GetCheck_NotFound(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV007") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) w := makeRequest(r, "GET", "/api/v1/inventory/checks/99999", token, nil) assert.Equal(t, http.StatusNotFound, w.Code) } func TestInventoryHandler_List_HotelIsolation(t *testing.T) { db := testutil.SetupTestDB() shopA := testutil.CreateTestShop(db, "INV_A") userA := testutil.CreateTestUser(db, shopA.ID, "adminA", "pass", "admin") warehouseA := testutil.CreateTestWarehouse(db, shopA.ID, "WA") productA := testutil.CreateTestProduct(db, shopA.ID, "ProductA") tokenA := getAuthToken(userA.ID, shopA.ID, "admin") shopB := testutil.CreateTestShop(db, "INV_B") userB := testutil.CreateTestUser(db, shopB.ID, "adminB", "pass", "admin") tokenB := getAuthToken(userB.ID, shopB.ID, "admin") r := setupProtectedRouter(db) // 酒店 A 有库存 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) respA := parseResponse(w) assert.Equal(t, float64(1), respA["total"].(float64)) // 酒店 B 看不到酒店 A 的库存 w = makeRequest(r, "GET", "/api/v1/inventory", tokenB, nil) respB := parseResponse(w) assert.Equal(t, float64(0), respB["total"].(float64)) } func TestInventoryHandler_AfterStockInApprove(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "INV008") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse") product := testutil.CreateTestProduct(db, shop.ID, "Champagne") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 初始库存为 0 w := makeRequest(r, "GET", "/api/v1/inventory", token, nil) assert.Equal(t, float64(0), parseResponse(w)["total"].(float64)) // 入库流程 w = makeRequest(r, "POST", "/api/v1/stock-in/orders", token, map[string]interface{}{ "warehouse_id": warehouse.ID, "order_date": time.Now().Format(time.RFC3339), "items": []map[string]interface{}{ {"product_id": product.ID, "quantity": 20.0, "unit_price": 15.0}, }, }) orderID := extractID(w) makeRequest(r, "PUT", fmt.Sprintf("/api/v1/stock-in/orders/%d/submit", orderID), token, nil) makeRequest(r, "PUT", fmt.Sprintf("/api/v1/stock-in/orders/%d/approve", orderID), token, nil) // 检查库存 w = makeRequest(r, "GET", "/api/v1/inventory", token, nil) resp := parseResponse(w) assert.Equal(t, float64(1), resp["total"].(float64)) data := resp["data"].([]interface{}) invItem := data[0].(map[string]interface{}) assert.Equal(t, float64(20), invItem["quantity"]) }