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
621 lines
16 KiB
Go
621 lines
16 KiB
Go
package testutil
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
"github.com/wangjia/jiu/backend/config"
|
|
"github.com/wangjia/jiu/backend/internal/middleware"
|
|
"github.com/wangjia/jiu/backend/internal/model"
|
|
)
|
|
|
|
// InitConfig 初始化测试用配置
|
|
func InitConfig() {
|
|
config.C = config.Config{
|
|
Server: config.ServerConfig{
|
|
Port: "8080",
|
|
Mode: "test",
|
|
},
|
|
JWT: config.JWTConfig{
|
|
Secret: "test-secret-key-for-testing",
|
|
AccessExpireMin: 60,
|
|
RefreshExpireH: 168,
|
|
},
|
|
Session: config.SessionConfig{
|
|
LimitTotal: 5,
|
|
LimitDesktop: 2,
|
|
LimitMobile: 2,
|
|
LimitWeb: 2,
|
|
MaxFailures: 5,
|
|
IPMaxFailures: 20,
|
|
LockMinutes: 15,
|
|
},
|
|
// 测试默认关闭限流,避免压测式用例触发 429;限流逻辑由 ratelimit_test.go 显式开启验证。
|
|
RateLimit: config.RateLimitConfig{Enabled: false},
|
|
}
|
|
}
|
|
|
|
// SetupTestDB 创建 SQLite in-memory 测试数据库并自动迁移
|
|
func SetupTestDB() *gorm.DB {
|
|
InitConfig()
|
|
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
TranslateError: true,
|
|
})
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to open sqlite: %v", err))
|
|
}
|
|
|
|
// SQLite 不支持 ENUM,直接用原始 SQL 建表
|
|
stmts := []string{
|
|
`CREATE TABLE IF NOT EXISTS shops (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
name TEXT NOT NULL,
|
|
code TEXT UNIQUE,
|
|
description TEXT,
|
|
address TEXT,
|
|
phone TEXT,
|
|
manager_name TEXT,
|
|
logo_url TEXT DEFAULT '',
|
|
wechat_id TEXT DEFAULT '',
|
|
business_license TEXT,
|
|
business_hours TEXT,
|
|
shop_photos TEXT,
|
|
custom_fields TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
username TEXT,
|
|
password_hash TEXT,
|
|
real_name TEXT,
|
|
phone TEXT,
|
|
role TEXT DEFAULT 'operator',
|
|
is_active INTEGER DEFAULT 1,
|
|
last_login_at DATETIME,
|
|
custom_fields TEXT
|
|
)`,
|
|
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_username ON users(shop_id, username)`,
|
|
`CREATE TABLE IF NOT EXISTS user_sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
shop_id INTEGER NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
sid TEXT NOT NULL UNIQUE,
|
|
device_id TEXT,
|
|
device_name TEXT,
|
|
platform TEXT,
|
|
platform_class TEXT,
|
|
ip TEXT,
|
|
user_agent TEXT,
|
|
refresh_jti TEXT,
|
|
created_at DATETIME,
|
|
last_seen_at DATETIME,
|
|
revoked_at DATETIME,
|
|
revoked_reason TEXT,
|
|
revoked_by INTEGER,
|
|
refresh_exp_at DATETIME,
|
|
kind TEXT DEFAULT 'login'
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS web_tickets (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ticket TEXT NOT NULL UNIQUE,
|
|
shop_id INTEGER NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
expires_at DATETIME NOT NULL,
|
|
used_at DATETIME,
|
|
created_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS login_attempts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
shop_code TEXT,
|
|
username TEXT,
|
|
ip TEXT,
|
|
user_agent TEXT,
|
|
success INTEGER DEFAULT 0,
|
|
reason TEXT,
|
|
created_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS licenses (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
license_key TEXT UNIQUE,
|
|
device_id TEXT,
|
|
type TEXT DEFAULT 'trial',
|
|
tier TEXT DEFAULT 'standard',
|
|
expires_at DATETIME,
|
|
is_active INTEGER DEFAULT 1,
|
|
max_devices INTEGER DEFAULT 3,
|
|
features TEXT,
|
|
activated_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS license_codes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code TEXT UNIQUE NOT NULL,
|
|
type TEXT DEFAULT 'annual',
|
|
tier TEXT DEFAULT 'standard',
|
|
duration_days INTEGER NOT NULL DEFAULT 0,
|
|
max_devices INTEGER NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'unused',
|
|
redeemed_shop_id INTEGER,
|
|
redeemed_at DATETIME,
|
|
redeemed_device_id TEXT,
|
|
batch TEXT,
|
|
note TEXT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS license_purchases (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
product_biz_code TEXT NOT NULL,
|
|
amount TEXT,
|
|
out_trade_no TEXT UNIQUE,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
trade_no TEXT,
|
|
channel TEXT,
|
|
paid_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS license_devices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
license_id INTEGER NOT NULL,
|
|
shop_id INTEGER NOT NULL,
|
|
device_id TEXT NOT NULL,
|
|
device_name TEXT,
|
|
platform TEXT,
|
|
activated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
last_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(license_id, device_id)
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_categories (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
name TEXT NOT NULL,
|
|
parent_id INTEGER,
|
|
sort_order INTEGER DEFAULT 0
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS products (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
public_id TEXT DEFAULT '',
|
|
code TEXT,
|
|
barcode TEXT,
|
|
name TEXT NOT NULL,
|
|
series TEXT,
|
|
spec TEXT,
|
|
unit TEXT,
|
|
category_id INTEGER,
|
|
brand TEXT,
|
|
purchase_price REAL,
|
|
sale_price REAL,
|
|
production_date DATETIME,
|
|
batch_no TEXT,
|
|
min_stock INTEGER DEFAULT 0,
|
|
description TEXT,
|
|
custom_fields TEXT,
|
|
remark TEXT,
|
|
name_pinyin TEXT DEFAULT '',
|
|
name_initials TEXT DEFAULT '',
|
|
origin_id INTEGER,
|
|
shelf_life_id INTEGER,
|
|
storage_id INTEGER,
|
|
description_doc_id INTEGER
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_origin_options (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
code TEXT,
|
|
name TEXT NOT NULL,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_shelf_life_options (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
code TEXT,
|
|
name TEXT NOT NULL,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_storage_options (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
code TEXT,
|
|
name TEXT NOT NULL,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_description_docs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
title TEXT NOT NULL,
|
|
content TEXT,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_images (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
product_id INTEGER NOT NULL,
|
|
shop_id INTEGER NOT NULL,
|
|
url TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_name_options (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
code TEXT,
|
|
name TEXT NOT NULL,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_series_options (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
code TEXT,
|
|
name TEXT NOT NULL,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS product_spec_options (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
code TEXT,
|
|
name TEXT NOT NULL,
|
|
quantity INTEGER DEFAULT 0,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS warehouses (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
name TEXT NOT NULL,
|
|
location TEXT,
|
|
is_default INTEGER DEFAULT 0,
|
|
custom_fields TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS partners (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
code TEXT,
|
|
name TEXT NOT NULL,
|
|
name_pinyin TEXT DEFAULT '',
|
|
name_initials TEXT DEFAULT '',
|
|
type TEXT DEFAULT 'supplier',
|
|
status TEXT NOT NULL DEFAULT 'enabled',
|
|
contact TEXT,
|
|
phone TEXT,
|
|
address TEXT,
|
|
bank_account TEXT,
|
|
credit_limit REAL,
|
|
balance REAL DEFAULT 0,
|
|
custom_fields TEXT,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS stock_in_orders (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
order_no TEXT,
|
|
type TEXT DEFAULT 'purchase',
|
|
warehouse_id INTEGER NOT NULL,
|
|
partner_id INTEGER,
|
|
operator_id INTEGER NOT NULL,
|
|
creator_id INTEGER,
|
|
reviewer_id INTEGER,
|
|
status TEXT DEFAULT 'draft',
|
|
order_date DATETIME,
|
|
cost_total REAL DEFAULT 0,
|
|
reviewed_at DATETIME,
|
|
return_state TEXT DEFAULT 'none',
|
|
custom_fields TEXT,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_sio_order_no ON stock_in_orders(shop_id, order_no)`,
|
|
`CREATE TABLE IF NOT EXISTS stock_in_items (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
order_id INTEGER NOT NULL,
|
|
shop_id INTEGER NOT NULL,
|
|
product_id INTEGER NOT NULL,
|
|
product_code TEXT,
|
|
product_name TEXT,
|
|
series TEXT,
|
|
spec TEXT,
|
|
quantity REAL NOT NULL,
|
|
cost_price REAL DEFAULT 0,
|
|
cost_amount REAL DEFAULT 0,
|
|
returned_quantity REAL DEFAULT 0,
|
|
batch_no TEXT,
|
|
production_date DATETIME,
|
|
custom_fields TEXT,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS stock_out_orders (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
order_no TEXT,
|
|
type TEXT DEFAULT 'sale',
|
|
warehouse_id INTEGER NOT NULL,
|
|
partner_id INTEGER,
|
|
operator_id INTEGER NOT NULL,
|
|
creator_id INTEGER,
|
|
reviewer_id INTEGER,
|
|
status TEXT DEFAULT 'draft',
|
|
order_date DATETIME,
|
|
sale_total REAL DEFAULT 0,
|
|
profit_total REAL DEFAULT 0,
|
|
reviewed_at DATETIME,
|
|
return_state TEXT DEFAULT 'none',
|
|
custom_fields TEXT,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_soo_order_no ON stock_out_orders(shop_id, order_no)`,
|
|
`CREATE TABLE IF NOT EXISTS stock_out_items (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
order_id INTEGER NOT NULL,
|
|
shop_id INTEGER NOT NULL,
|
|
product_id INTEGER NOT NULL,
|
|
product_code TEXT,
|
|
product_name TEXT,
|
|
series TEXT,
|
|
spec TEXT,
|
|
quantity REAL NOT NULL,
|
|
cost_price REAL DEFAULT 0,
|
|
sale_price REAL DEFAULT 0,
|
|
cost_amount REAL DEFAULT 0,
|
|
sale_amount REAL DEFAULT 0,
|
|
returned_quantity REAL DEFAULT 0,
|
|
batch_no TEXT,
|
|
production_date DATETIME,
|
|
custom_fields TEXT,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS inventories (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
shop_id INTEGER NOT NULL,
|
|
warehouse_id INTEGER,
|
|
product_id INTEGER,
|
|
stock_in_item_id INTEGER,
|
|
inventory_check_id INTEGER,
|
|
quantity REAL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'stock',
|
|
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,
|
|
deleted_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS inventory_logs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
shop_id INTEGER NOT NULL,
|
|
warehouse_id INTEGER NOT NULL,
|
|
product_id INTEGER NOT NULL,
|
|
direction TEXT,
|
|
quantity REAL,
|
|
qty_before REAL,
|
|
qty_after REAL,
|
|
ref_type TEXT,
|
|
ref_id INTEGER,
|
|
operator_id INTEGER,
|
|
created_at DATETIME
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS inventory_checks (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
shop_id INTEGER NOT NULL,
|
|
check_no TEXT,
|
|
warehouse_id INTEGER NOT NULL,
|
|
operator_id INTEGER NOT NULL,
|
|
status TEXT DEFAULT 'draft',
|
|
check_date DATETIME,
|
|
remark TEXT
|
|
)`,
|
|
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_check_no ON inventory_checks(shop_id, check_no)`,
|
|
`CREATE TABLE IF NOT EXISTS inventory_check_items (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME,
|
|
updated_at DATETIME,
|
|
deleted_at DATETIME,
|
|
check_id INTEGER NOT NULL,
|
|
shop_id INTEGER NOT NULL,
|
|
product_id INTEGER NOT NULL,
|
|
system_qty REAL,
|
|
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,
|
|
type TEXT,
|
|
prefix TEXT DEFAULT '',
|
|
current_no INTEGER DEFAULT 0,
|
|
date_format TEXT DEFAULT 'YYYYMMDD',
|
|
updated_at DATETIME,
|
|
UNIQUE(shop_id, type)
|
|
)`,
|
|
}
|
|
|
|
for _, stmt := range stmts {
|
|
if err := db.Exec(stmt).Error; err != nil {
|
|
panic(fmt.Sprintf("failed to create table: %v\nSQL: %s", err, stmt))
|
|
}
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
// CreateTestShop 创建测试门店
|
|
func CreateTestShop(db *gorm.DB, code string) *model.Shop {
|
|
shop := &model.Shop{
|
|
Name: "Test Shop " + code,
|
|
Code: code,
|
|
}
|
|
if err := db.Create(shop).Error; err != nil {
|
|
panic(fmt.Sprintf("failed to create test shop: %v", err))
|
|
}
|
|
return shop
|
|
}
|
|
|
|
// hashPassword 内部使用的密码哈希函数,避免循环依赖
|
|
func hashPassword(plain string) string {
|
|
b, err := bcrypt.GenerateFromPassword([]byte(plain), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to hash password: %v", err))
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// CreateTestUser 创建测试用户
|
|
func CreateTestUser(db *gorm.DB, shopID uint64, username, password, role string) *model.User {
|
|
hash := hashPassword(password)
|
|
user := &model.User{
|
|
ShopID: shopID,
|
|
Username: username,
|
|
PasswordHash: hash,
|
|
RealName: "Test User " + username,
|
|
Role: role,
|
|
IsActive: true,
|
|
}
|
|
if err := db.Create(user).Error; err != nil {
|
|
panic(fmt.Sprintf("failed to create test user: %v", err))
|
|
}
|
|
return user
|
|
}
|
|
|
|
// CreateTestWarehouse 创建测试仓库
|
|
func CreateTestWarehouse(db *gorm.DB, shopID uint64, name string) *model.Warehouse {
|
|
w := &model.Warehouse{
|
|
TenantBase: model.TenantBase{
|
|
ShopID: shopID,
|
|
},
|
|
Name: name,
|
|
IsDefault: true,
|
|
}
|
|
if err := db.Create(w).Error; err != nil {
|
|
panic(fmt.Sprintf("failed to create test warehouse: %v", err))
|
|
}
|
|
return w
|
|
}
|
|
|
|
// CreateTestProduct 创建测试商品
|
|
func CreateTestProduct(db *gorm.DB, shopID uint64, name string) *model.Product {
|
|
p := &model.Product{
|
|
TenantBase: model.TenantBase{
|
|
ShopID: shopID,
|
|
},
|
|
Name: name,
|
|
Code: "P-" + name,
|
|
Unit: "个",
|
|
}
|
|
if err := db.Create(p).Error; err != nil {
|
|
panic(fmt.Sprintf("failed to create test product: %v", err))
|
|
}
|
|
return p
|
|
}
|
|
|
|
// GetAuthToken 生成测试 JWT token
|
|
func GetAuthToken(userID, shopID uint64, role string) string {
|
|
InitConfig()
|
|
now := time.Now()
|
|
claims := middleware.Claims{
|
|
UserID: userID,
|
|
ShopID: shopID,
|
|
Role: role,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(time.Hour)),
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
},
|
|
}
|
|
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).
|
|
SignedString([]byte(config.C.JWT.Secret))
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to sign token: %v", err))
|
|
}
|
|
return token
|
|
}
|