fix(backend): JWT config mapstructure tag 修复 + 模型从 hotel 重构为 shop
- 修复 JWTConfig 缺少 mapstructure tag 导致 access_expire_min 解析为 0, token 签发即过期,所有 API 请求返回 401 - 全部 config struct 补齐 mapstructure tag(secret/dsn/hmac_secret 等) - 模型层从 hotel/HotelID 统一重命名为 shop/ShopID - 删除旧 migrations(001-004),新增 001_init 综合迁移文件 - 更新 schema.sql、testutil、handler/service/model 相关引用 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,14 +15,14 @@ import (
|
||||
|
||||
func TestStockService_ApproveStockIn_Success(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK001")
|
||||
warehouse := testutil.CreateTestWarehouse(db, hotel.ID, "Main Warehouse")
|
||||
product := testutil.CreateTestProduct(db, hotel.ID, "Beer")
|
||||
user := testutil.CreateTestUser(db, hotel.ID, "admin", "pass", "admin")
|
||||
shop := testutil.CreateTestShop(db, "STOCK001")
|
||||
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Main Warehouse")
|
||||
product := testutil.CreateTestProduct(db, shop.ID, "Beer")
|
||||
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
||||
|
||||
// 创建入库单
|
||||
order := model.StockInOrder{
|
||||
TenantBase: model.TenantBase{HotelID: hotel.ID},
|
||||
TenantBase: model.TenantBase{ShopID: shop.ID},
|
||||
OrderNo: "IN20240101000001",
|
||||
WarehouseID: warehouse.ID,
|
||||
OperatorID: user.ID,
|
||||
@@ -30,7 +30,7 @@ func TestStockService_ApproveStockIn_Success(t *testing.T) {
|
||||
OrderDate: time.Now(),
|
||||
Items: []model.StockInItem{
|
||||
{
|
||||
HotelID: hotel.ID,
|
||||
ShopID: shop.ID,
|
||||
ProductID: product.ID,
|
||||
Quantity: 10,
|
||||
UnitPrice: 5.0,
|
||||
@@ -41,7 +41,7 @@ func TestStockService_ApproveStockIn_Success(t *testing.T) {
|
||||
require.NoError(t, db.Create(&order).Error)
|
||||
|
||||
svc := NewStockService(db)
|
||||
err := svc.ApproveStockIn(hotel.ID, order.ID, user.ID)
|
||||
err := svc.ApproveStockIn(shop.ID, order.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 验证单据状态
|
||||
@@ -51,13 +51,13 @@ func TestStockService_ApproveStockIn_Success(t *testing.T) {
|
||||
|
||||
// 验证库存
|
||||
var inv model.Inventory
|
||||
db.Where("hotel_id = ? AND warehouse_id = ? AND product_id = ?",
|
||||
hotel.ID, warehouse.ID, product.ID).First(&inv)
|
||||
db.Where("shop_id = ? AND warehouse_id = ? AND product_id = ?",
|
||||
shop.ID, warehouse.ID, product.ID).First(&inv)
|
||||
assert.Equal(t, float64(10), inv.Quantity)
|
||||
|
||||
// 验证库存流水
|
||||
var logs []model.InventoryLog
|
||||
db.Where("hotel_id = ? AND product_id = ?", hotel.ID, product.ID).Find(&logs)
|
||||
db.Where("shop_id = ? AND product_id = ?", shop.ID, product.ID).Find(&logs)
|
||||
require.Len(t, logs, 1)
|
||||
assert.Equal(t, "in", logs[0].Direction)
|
||||
assert.Equal(t, float64(10), logs[0].Quantity)
|
||||
@@ -67,13 +67,13 @@ func TestStockService_ApproveStockIn_Success(t *testing.T) {
|
||||
|
||||
func TestStockService_ApproveStockIn_NotPending(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK002")
|
||||
warehouse := testutil.CreateTestWarehouse(db, hotel.ID, "Warehouse")
|
||||
_ = testutil.CreateTestProduct(db, hotel.ID, "Wine")
|
||||
user := testutil.CreateTestUser(db, hotel.ID, "admin", "pass", "admin")
|
||||
shop := testutil.CreateTestShop(db, "STOCK002")
|
||||
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse")
|
||||
_ = testutil.CreateTestProduct(db, shop.ID, "Wine")
|
||||
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
||||
|
||||
order := model.StockInOrder{
|
||||
TenantBase: model.TenantBase{HotelID: hotel.ID},
|
||||
TenantBase: model.TenantBase{ShopID: shop.ID},
|
||||
OrderNo: "IN20240101000002",
|
||||
WarehouseID: warehouse.ID,
|
||||
OperatorID: user.ID,
|
||||
@@ -83,21 +83,21 @@ func TestStockService_ApproveStockIn_NotPending(t *testing.T) {
|
||||
require.NoError(t, db.Create(&order).Error)
|
||||
|
||||
svc := NewStockService(db)
|
||||
err := svc.ApproveStockIn(hotel.ID, order.ID, user.ID)
|
||||
err := svc.ApproveStockIn(shop.ID, order.ID, user.ID)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not in pending status")
|
||||
}
|
||||
|
||||
func TestStockService_ApproveStockOut_Success(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK003")
|
||||
warehouse := testutil.CreateTestWarehouse(db, hotel.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, hotel.ID, "Whiskey")
|
||||
user := testutil.CreateTestUser(db, hotel.ID, "admin", "pass", "admin")
|
||||
shop := testutil.CreateTestShop(db, "STOCK003")
|
||||
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, shop.ID, "Whiskey")
|
||||
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
||||
|
||||
// 先入库
|
||||
inv := model.Inventory{
|
||||
HotelID: hotel.ID,
|
||||
ShopID: shop.ID,
|
||||
WarehouseID: warehouse.ID,
|
||||
ProductID: product.ID,
|
||||
Quantity: 20,
|
||||
@@ -106,7 +106,7 @@ func TestStockService_ApproveStockOut_Success(t *testing.T) {
|
||||
|
||||
// 创建出库单
|
||||
order := model.StockOutOrder{
|
||||
TenantBase: model.TenantBase{HotelID: hotel.ID},
|
||||
TenantBase: model.TenantBase{ShopID: shop.ID},
|
||||
OrderNo: "OUT20240101000001",
|
||||
WarehouseID: warehouse.ID,
|
||||
OperatorID: user.ID,
|
||||
@@ -114,7 +114,7 @@ func TestStockService_ApproveStockOut_Success(t *testing.T) {
|
||||
OrderDate: time.Now(),
|
||||
Items: []model.StockOutItem{
|
||||
{
|
||||
HotelID: hotel.ID,
|
||||
ShopID: shop.ID,
|
||||
ProductID: product.ID,
|
||||
Quantity: 5,
|
||||
UnitPrice: 10.0,
|
||||
@@ -125,7 +125,7 @@ func TestStockService_ApproveStockOut_Success(t *testing.T) {
|
||||
require.NoError(t, db.Create(&order).Error)
|
||||
|
||||
svc := NewStockService(db)
|
||||
err := svc.ApproveStockOut(hotel.ID, order.ID, user.ID)
|
||||
err := svc.ApproveStockOut(shop.ID, order.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 验证库存减少
|
||||
@@ -141,14 +141,14 @@ func TestStockService_ApproveStockOut_Success(t *testing.T) {
|
||||
|
||||
func TestStockService_ApproveStockOut_InsufficientStock(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK004")
|
||||
warehouse := testutil.CreateTestWarehouse(db, hotel.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, hotel.ID, "Vodka")
|
||||
user := testutil.CreateTestUser(db, hotel.ID, "admin", "pass", "admin")
|
||||
shop := testutil.CreateTestShop(db, "STOCK004")
|
||||
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, shop.ID, "Vodka")
|
||||
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
||||
|
||||
// 库存只有 3
|
||||
inv := model.Inventory{
|
||||
HotelID: hotel.ID,
|
||||
ShopID: shop.ID,
|
||||
WarehouseID: warehouse.ID,
|
||||
ProductID: product.ID,
|
||||
Quantity: 3,
|
||||
@@ -157,7 +157,7 @@ func TestStockService_ApproveStockOut_InsufficientStock(t *testing.T) {
|
||||
|
||||
// 要出库 10
|
||||
order := model.StockOutOrder{
|
||||
TenantBase: model.TenantBase{HotelID: hotel.ID},
|
||||
TenantBase: model.TenantBase{ShopID: shop.ID},
|
||||
OrderNo: "OUT20240101000002",
|
||||
WarehouseID: warehouse.ID,
|
||||
OperatorID: user.ID,
|
||||
@@ -165,7 +165,7 @@ func TestStockService_ApproveStockOut_InsufficientStock(t *testing.T) {
|
||||
OrderDate: time.Now(),
|
||||
Items: []model.StockOutItem{
|
||||
{
|
||||
HotelID: hotel.ID,
|
||||
ShopID: shop.ID,
|
||||
ProductID: product.ID,
|
||||
Quantity: 10,
|
||||
},
|
||||
@@ -174,21 +174,21 @@ func TestStockService_ApproveStockOut_InsufficientStock(t *testing.T) {
|
||||
require.NoError(t, db.Create(&order).Error)
|
||||
|
||||
svc := NewStockService(db)
|
||||
err := svc.ApproveStockOut(hotel.ID, order.ID, user.ID)
|
||||
err := svc.ApproveStockOut(shop.ID, order.ID, user.ID)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrInsufficientStock)
|
||||
}
|
||||
|
||||
func TestStockService_ApproveStockOut_ProductNotInInventory(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK005")
|
||||
warehouse := testutil.CreateTestWarehouse(db, hotel.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, hotel.ID, "Rum")
|
||||
user := testutil.CreateTestUser(db, hotel.ID, "admin", "pass", "admin")
|
||||
shop := testutil.CreateTestShop(db, "STOCK005")
|
||||
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, shop.ID, "Rum")
|
||||
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
||||
|
||||
// 没有创建库存记录
|
||||
order := model.StockOutOrder{
|
||||
TenantBase: model.TenantBase{HotelID: hotel.ID},
|
||||
TenantBase: model.TenantBase{ShopID: shop.ID},
|
||||
OrderNo: "OUT20240101000003",
|
||||
WarehouseID: warehouse.ID,
|
||||
OperatorID: user.ID,
|
||||
@@ -196,7 +196,7 @@ func TestStockService_ApproveStockOut_ProductNotInInventory(t *testing.T) {
|
||||
OrderDate: time.Now(),
|
||||
Items: []model.StockOutItem{
|
||||
{
|
||||
HotelID: hotel.ID,
|
||||
ShopID: shop.ID,
|
||||
ProductID: product.ID,
|
||||
Quantity: 5,
|
||||
},
|
||||
@@ -205,24 +205,24 @@ func TestStockService_ApproveStockOut_ProductNotInInventory(t *testing.T) {
|
||||
require.NoError(t, db.Create(&order).Error)
|
||||
|
||||
svc := NewStockService(db)
|
||||
err := svc.ApproveStockOut(hotel.ID, order.ID, user.ID)
|
||||
err := svc.ApproveStockOut(shop.ID, order.ID, user.ID)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not in inventory")
|
||||
}
|
||||
|
||||
func TestStockService_GenerateOrderNo(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK006")
|
||||
shop := testutil.CreateTestShop(db, "STOCK006")
|
||||
|
||||
svc := NewStockService(db)
|
||||
no, err := svc.GenerateOrderNo(hotel.ID, "stock_in")
|
||||
no, err := svc.GenerateOrderNo(shop.ID, "stock_in")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, no)
|
||||
// 单号格式: prefix + date + 6位序号
|
||||
assert.Contains(t, no, "st")
|
||||
|
||||
// 第二次生成序号应递增
|
||||
no2, err := svc.GenerateOrderNo(hotel.ID, "stock_in")
|
||||
no2, err := svc.GenerateOrderNo(shop.ID, "stock_in")
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, no, no2)
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func TestStockService_GenerateOrderNo(t *testing.T) {
|
||||
// 注:并发安全由 MySQL 的事务锁保证,SQLite in-memory 不模拟此场景
|
||||
func TestStockService_GenerateOrderNo_Sequential(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK007")
|
||||
shop := testutil.CreateTestShop(db, "STOCK007")
|
||||
|
||||
svc := NewStockService(db)
|
||||
|
||||
@@ -239,7 +239,7 @@ func TestStockService_GenerateOrderNo_Sequential(t *testing.T) {
|
||||
seen := map[string]bool{}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
no, err := svc.GenerateOrderNo(hotel.ID, "stock_in")
|
||||
no, err := svc.GenerateOrderNo(shop.ID, "stock_in")
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, no)
|
||||
assert.False(t, seen[no], fmt.Sprintf("duplicate order no: %s", no))
|
||||
@@ -251,46 +251,46 @@ func TestStockService_GenerateOrderNo_Sequential(t *testing.T) {
|
||||
|
||||
func TestStockService_MultipleStockIn_AccumulatesInventory(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
hotel := testutil.CreateTestHotel(db, "STOCK008")
|
||||
warehouse := testutil.CreateTestWarehouse(db, hotel.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, hotel.ID, "Gin")
|
||||
user := testutil.CreateTestUser(db, hotel.ID, "admin", "pass", "admin")
|
||||
shop := testutil.CreateTestShop(db, "STOCK008")
|
||||
warehouse := testutil.CreateTestWarehouse(db, shop.ID, "Warehouse")
|
||||
product := testutil.CreateTestProduct(db, shop.ID, "Gin")
|
||||
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
||||
|
||||
svc := NewStockService(db)
|
||||
|
||||
// 第一次入库
|
||||
order1 := model.StockInOrder{
|
||||
TenantBase: model.TenantBase{HotelID: hotel.ID},
|
||||
TenantBase: model.TenantBase{ShopID: shop.ID},
|
||||
OrderNo: "IN001",
|
||||
WarehouseID: warehouse.ID,
|
||||
OperatorID: user.ID,
|
||||
Status: "pending",
|
||||
OrderDate: time.Now(),
|
||||
Items: []model.StockInItem{
|
||||
{HotelID: hotel.ID, ProductID: product.ID, Quantity: 10},
|
||||
{ShopID: shop.ID, ProductID: product.ID, Quantity: 10},
|
||||
},
|
||||
}
|
||||
require.NoError(t, db.Create(&order1).Error)
|
||||
require.NoError(t, svc.ApproveStockIn(hotel.ID, order1.ID, user.ID))
|
||||
require.NoError(t, svc.ApproveStockIn(shop.ID, order1.ID, user.ID))
|
||||
|
||||
// 第二次入库
|
||||
order2 := model.StockInOrder{
|
||||
TenantBase: model.TenantBase{HotelID: hotel.ID},
|
||||
TenantBase: model.TenantBase{ShopID: shop.ID},
|
||||
OrderNo: "IN002",
|
||||
WarehouseID: warehouse.ID,
|
||||
OperatorID: user.ID,
|
||||
Status: "pending",
|
||||
OrderDate: time.Now(),
|
||||
Items: []model.StockInItem{
|
||||
{HotelID: hotel.ID, ProductID: product.ID, Quantity: 5},
|
||||
{ShopID: shop.ID, ProductID: product.ID, Quantity: 5},
|
||||
},
|
||||
}
|
||||
require.NoError(t, db.Create(&order2).Error)
|
||||
require.NoError(t, svc.ApproveStockIn(hotel.ID, order2.ID, user.ID))
|
||||
require.NoError(t, svc.ApproveStockIn(shop.ID, order2.ID, user.ID))
|
||||
|
||||
// 总库存应该是 15
|
||||
var inv model.Inventory
|
||||
db.Where("hotel_id = ? AND warehouse_id = ? AND product_id = ?",
|
||||
hotel.ID, warehouse.ID, product.ID).First(&inv)
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user