feat(backend): 库存按商品过滤 + 香型(分类)字典 CRUD

商品域富数据/重组所需后端:
- GET /inventory 加 product_id 过滤 → 解锁商品详情「当前库存/货值/各仓库分布」
  (配合已支持 product_id 的 /inventory/logs,详情可拼出大半富数据)
- 新增 /product-options/categories 香型字典 CRUD(ProductCategory 模型已存在,
  补管理接口)→ 商品列表 5-tab 重组的最后一块字典
均守多租户 shop_id;新增 3 个测试(FilterByProduct/CategoryCRUD/CategoryIsolation)。
go build/vet/test 全过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
This commit is contained in:
wangjia
2026-06-25 15:07:49 +08:00
parent 1b8bf596c5
commit 90ab22bf0e
5 changed files with 169 additions and 0 deletions
@@ -59,6 +59,28 @@ func TestInventoryHandler_List_FilterByWarehouse(t *testing.T) {
assert.Equal(t, float64(1), resp["total"].(float64))
}
func TestInventoryHandler_List_FilterByProduct(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "INV_PID")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
wh1 := testutil.CreateTestWarehouse(db, shop.ID, "W1")
wh2 := 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)
// product1 在两个仓库各有库存;product2 一条
db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &wh1.ID, ProductID: &product1.ID, Quantity: 10})
db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &wh2.ID, ProductID: &product1.ID, Quantity: 20})
db.Create(&model.Inventory{ShopID: shop.ID, WarehouseID: &wh1.ID, ProductID: &product2.ID, Quantity: 5})
// 按商品过滤 → 只返回 product1 的两条(商品详情:各仓库分布)
w := makeRequest(r, "GET", fmt.Sprintf("/api/v1/inventory?product_id=%d&page_size=100", product1.ID), token, nil)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, float64(2), parseResponse(w)["total"].(float64))
}
func TestInventoryHandler_List_InStockOnly(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "INV003")