73955a139c
- inventories 加 status 枚举列(默认 stock,AutoMigrate 存量即全部「库存」)+ 索引 - 出库审核扣光置 sold 不再软删(留痕台账);退单恢复行置回 stock;盘亏仍软删 - 新接口 PUT /inventory/:id/status(仅收 stock/on_sale,已售行拒改,租户校验) - List 默认排除已售(显式 status=sold 才显示)+ status 多值筛选 - Summary:SKU/货值/数量排除已售,新增 sold_count - 公开店铺页/API 只列 on_sale——酒单从全量裸奔改为人工精选 - 测试:卖光置售/部分不改/退单回库/接口边界/跨租户/汇总口径 8 用例 - 设计方案 docs/design/inventory-sale-status.html + db-schema/CLAUDE.md 同步 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
package handler
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/wangjia/jiu/backend/internal/model"
|
||
"github.com/wangjia/jiu/backend/testutil"
|
||
)
|
||
|
||
func setupPublicRouter(db *gorm.DB) *gin.Engine {
|
||
h := NewPublicHandler(db)
|
||
r := gin.New()
|
||
r.Use(gin.Recovery())
|
||
r.GET("/api/v1/public/shops/:shop_code/products", h.ListShopProducts)
|
||
return r
|
||
}
|
||
|
||
// 给商品补 public_id(CreateTestProduct 默认不设)
|
||
func setPublicID(db *gorm.DB, productID uint64, pub string) {
|
||
require := func(err error) {
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
require(db.Model(&model.Product{}).Where("id = ?", productID).Update("public_id", pub).Error)
|
||
}
|
||
|
||
func addInventory(db *gorm.DB, shopID, warehouseID, productID uint64, qty float64) {
|
||
wid := warehouseID
|
||
pid := productID
|
||
if err := db.Create(&model.Inventory{
|
||
ShopID: shopID,
|
||
WarehouseID: &wid,
|
||
ProductID: &pid,
|
||
Quantity: qty,
|
||
Status: "on_sale", // 公开页 fixtures:三态改造后酒单只挂在售
|
||
}).Error; err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
|
||
func TestPublicHandler_ListShopProducts_InStockOnly(t *testing.T) {
|
||
db := testutil.SetupTestDB()
|
||
shop := testutil.CreateTestShop(db, "PUB001")
|
||
wh := testutil.CreateTestWarehouse(db, shop.ID, "仓")
|
||
r := setupPublicRouter(db)
|
||
|
||
// A:有库存 5 → 应出现,quantity=5
|
||
pa := testutil.CreateTestProduct(db, shop.ID, "茅台A")
|
||
setPublicID(db, pa.ID, "pub-a")
|
||
addInventory(db, shop.ID, wh.ID, pa.ID, 5)
|
||
|
||
// B:有 public_id 但无库存 → 不出现
|
||
pb := testutil.CreateTestProduct(db, shop.ID, "五粮液B")
|
||
setPublicID(db, pb.ID, "pub-b")
|
||
|
||
// C:两条库存 3+2 → 出现,quantity=5
|
||
pc := testutil.CreateTestProduct(db, shop.ID, "汾酒C")
|
||
setPublicID(db, pc.ID, "pub-c")
|
||
addInventory(db, shop.ID, wh.ID, pc.ID, 3)
|
||
addInventory(db, shop.ID, wh.ID, pc.ID, 2)
|
||
|
||
// D:有库存但无 public_id → 不出现
|
||
pd := testutil.CreateTestProduct(db, shop.ID, "无公开D")
|
||
addInventory(db, shop.ID, wh.ID, pd.ID, 9)
|
||
|
||
w := httptest.NewRecorder()
|
||
req, _ := http.NewRequest("GET", "/api/v1/public/shops/PUB001/products", nil)
|
||
r.ServeHTTP(w, req)
|
||
require.Equal(t, http.StatusOK, w.Code)
|
||
|
||
var resp map[string]interface{}
|
||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||
|
||
// 只 A、C 两个有库存 + 有 public_id
|
||
assert.Equal(t, float64(2), resp["total"])
|
||
data := resp["data"].([]interface{})
|
||
require.Len(t, data, 2)
|
||
|
||
byName := map[string]map[string]interface{}{}
|
||
for _, it := range data {
|
||
m := it.(map[string]interface{})
|
||
byName[m["name"].(string)] = m
|
||
}
|
||
|
||
require.Contains(t, byName, "茅台A")
|
||
require.Contains(t, byName, "汾酒C")
|
||
assert.NotContains(t, byName, "五粮液B") // 无库存
|
||
assert.NotContains(t, byName, "无公开D") // 无 public_id
|
||
|
||
// 数量正确(C 聚合 3+2=5)
|
||
assert.Equal(t, float64(5), byName["茅台A"]["quantity"])
|
||
assert.Equal(t, float64(5), byName["汾酒C"]["quantity"])
|
||
// 带上了序列号 code
|
||
assert.Equal(t, "P-茅台A", byName["茅台A"]["code"])
|
||
}
|
||
|
||
func TestPublicHandler_ListShopProducts_Isolation(t *testing.T) {
|
||
db := testutil.SetupTestDB()
|
||
shopA := testutil.CreateTestShop(db, "PUBA")
|
||
shopB := testutil.CreateTestShop(db, "PUBB")
|
||
whB := testutil.CreateTestWarehouse(db, shopB.ID, "仓B")
|
||
r := setupPublicRouter(db)
|
||
|
||
// B 店有个有库存商品
|
||
pb := testutil.CreateTestProduct(db, shopB.ID, "他店商品")
|
||
setPublicID(db, pb.ID, "pub-x")
|
||
addInventory(db, shopB.ID, whB.ID, pb.ID, 7)
|
||
|
||
// 查 A 店:看不到 B 店商品
|
||
w := httptest.NewRecorder()
|
||
req, _ := http.NewRequest("GET", "/api/v1/public/shops/"+shopA.Code+"/products", nil)
|
||
r.ServeHTTP(w, req)
|
||
require.Equal(t, http.StatusOK, w.Code)
|
||
var resp map[string]interface{}
|
||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||
assert.Equal(t, float64(0), resp["total"])
|
||
}
|