feat: 增加顺运宝店铺筛选 (#151)
This commit is contained in:
+38
-1
@@ -265,7 +265,7 @@ var migrations = [][]string{
|
||||
// 背景见 #20:v1 曾经被原地改写而不是新增版本,导致已经建过库的机器
|
||||
// (user_version 已经越过 v1)永远不会重跑改写后的语句,程序拿着一个
|
||||
// 和代码对不上的库静默启动。
|
||||
const schemaVersion = 8
|
||||
const schemaVersion = 9
|
||||
|
||||
// migrationV4 给 PDD 商品增加店铺名。
|
||||
//
|
||||
@@ -380,6 +380,18 @@ var migrationV8 = []string{
|
||||
ON syb_sync_runs(started_at DESC, run_id DESC);`,
|
||||
}
|
||||
|
||||
// migrationV9 把顺运宝原始 JSON 里的货运单店铺名提升为可展示、可筛选的独立列。
|
||||
// 原始 syb_data 永久保留;无效 JSON、缺字段和纯空白值都安全地保持 NULL。
|
||||
var migrationV9 = []string{
|
||||
`ALTER TABLE syb_orders ADD COLUMN shop_name TEXT;`,
|
||||
`UPDATE syb_orders
|
||||
SET shop_name = CASE
|
||||
WHEN json_valid(syb_data) THEN NULLIF(TRIM(json_extract(syb_data, '$.stock.shopName')), '')
|
||||
ELSE NULL
|
||||
END
|
||||
WHERE shop_name IS NULL;`,
|
||||
}
|
||||
|
||||
// Migrate 把数据库升到最新版本。
|
||||
// 已经是最新的就什么都不做,可以重复调用。
|
||||
func Migrate(db *sql.DB) error {
|
||||
@@ -466,6 +478,14 @@ func Migrate(db *sql.DB) error {
|
||||
if err := runSQLMigration(db, 8, migrationV8); err != nil {
|
||||
return err
|
||||
}
|
||||
reached = 8
|
||||
}
|
||||
|
||||
// v9 增加顺运宝店铺列并从仍保留的原始 JSON 回填。
|
||||
if reached < 9 {
|
||||
if err := runSQLMigration(db, 9, migrationV9); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -940,6 +960,12 @@ var requiredColumns = map[string][]string{
|
||||
"syb_sync_runs": {"user_id", "date_from", "date_to", "status", "cursor_advanced", "started_at", "finished_at"},
|
||||
}
|
||||
|
||||
// SQLite 当前测试库比 MySQL v1 多走一版迁移;这里单独检查,避免 MySQL
|
||||
// 在执行 v1 自检时提前要求尚未由 v11 创建的列。
|
||||
var sqliteOnlyRequiredColumns = map[string][]string{
|
||||
"syb_orders": {"shop_name"},
|
||||
}
|
||||
|
||||
// CheckSchema 在 Migrate 成功后调用,确认代码依赖的表都在。
|
||||
//
|
||||
// [必须] 缺表就返回错误,调用方要**拒绝启动**,不是打个警告继续跑。
|
||||
@@ -996,5 +1022,16 @@ func CheckSchema(db *sql.DB) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
for table, columns := range sqliteOnlyRequiredColumns {
|
||||
existingColumns, err := tableColumnSet(db, table)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查数据表 %s 的列失败: %w", table, err)
|
||||
}
|
||||
for _, column := range columns {
|
||||
if !existingColumns[column] {
|
||||
return fmt.Errorf("数据库结构与本程序不匹配:数据表 %s 缺少列 %s", table, column)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -657,7 +657,7 @@ func TestMigrate_v8新增顺运宝同步记录表(t *testing.T) {
|
||||
t.Fatal("v8 应该新增 syb_sync_runs 表")
|
||||
}
|
||||
var version int
|
||||
if err := db.QueryRow(`PRAGMA user_version`).Scan(&version); err != nil || version != 8 {
|
||||
if err := db.QueryRow(`PRAGMA user_version`).Scan(&version); err != nil || version != schemaVersion {
|
||||
t.Fatalf("迁移版本错误: version=%d err=%v", version, err)
|
||||
}
|
||||
user := model.User{
|
||||
@@ -683,6 +683,41 @@ func TestMigrate_v8新增顺运宝同步记录表(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrate_v9回填顺运宝店铺名(t *testing.T) {
|
||||
db := newPublishedVersionDB(t, 7)
|
||||
if err := runSQLMigration(db, 8, migrationV8); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := model.NowISO()
|
||||
for _, row := range []struct{ id, raw string }{
|
||||
{"shop", `{"stock":{"shopName":" 测试店铺 "}}`},
|
||||
{"blank", `{"stock":{"shopName":" "}}`},
|
||||
{"missing", `{"stock":{}}`},
|
||||
{"invalid", `{not-json`},
|
||||
} {
|
||||
if _, err := db.Exec(`INSERT INTO syb_orders
|
||||
(syb_id,order_no,title,product_spec,quantity,syb_data,created_at,updated_at)
|
||||
VALUES (?,?,'商品','黑色,M',1,?,?,?)`, row.id, row.id, row.raw, now, now); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := Migrate(db); err != nil {
|
||||
t.Fatalf("v8 升级 v9 失败: %v", err)
|
||||
}
|
||||
var shop sql.NullString
|
||||
if err := db.QueryRow(`SELECT shop_name FROM syb_orders WHERE syb_id='shop'`).Scan(&shop); err != nil || !shop.Valid || shop.String != "测试店铺" {
|
||||
t.Fatalf("店铺回填错误: %+v err=%v", shop, err)
|
||||
}
|
||||
for _, id := range []string{"blank", "missing", "invalid"} {
|
||||
if err := db.QueryRow(`SELECT shop_name FROM syb_orders WHERE syb_id=?`, id).Scan(&shop); err != nil || shop.Valid {
|
||||
t.Fatalf("%s 应保持 NULL: %+v err=%v", id, shop, err)
|
||||
}
|
||||
}
|
||||
if err := Migrate(db); err != nil {
|
||||
t.Fatalf("v9 重复迁移失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newPublishedVersionDB(t *testing.T, version int) *sql.DB {
|
||||
t.Helper()
|
||||
db, err := Open(t.TempDir())
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"cmautobuy/admin/spec"
|
||||
)
|
||||
|
||||
const mysqlSchemaVersion = 10
|
||||
const mysqlSchemaVersion = 11
|
||||
|
||||
// OpenMySQL 打开生产 MySQL 8 数据库。错误信息绝不包含完整 DSN 或密码。
|
||||
func OpenMySQL(cfg config.DatabaseConfig) (*sql.DB, error) {
|
||||
@@ -543,10 +543,50 @@ func MigrateMySQL(db *sql.DB) error {
|
||||
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 10, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
|
||||
return fmt.Errorf("记录 MySQL schema v10 失败: %w", err)
|
||||
}
|
||||
current = 10
|
||||
}
|
||||
if current < 11 {
|
||||
if err := migrateMySQLV11(db); err != nil {
|
||||
return fmt.Errorf("执行 MySQL schema v11 失败: %w", err)
|
||||
}
|
||||
if err := checkMySQLV11Shape(db); err != nil {
|
||||
return fmt.Errorf("MySQL schema v11 自检失败,未记录版本: %w", err)
|
||||
}
|
||||
if _, err := db.Exec(`INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)`, 11, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
|
||||
return fmt.Errorf("记录 MySQL schema v11 失败: %w", err)
|
||||
}
|
||||
}
|
||||
return CheckMySQLSchema(db)
|
||||
}
|
||||
|
||||
// migrateMySQLV11 增加顺运宝店铺列,并从保留的原始 JSON 回填历史数据。
|
||||
// DDL 和 UPDATE 都可重放:中断后再次启动只补缺列和仍为空的记录。
|
||||
func migrateMySQLV11(db *sql.DB) error {
|
||||
exists, err := mysqlColumnExists(db, "syb_orders", "shop_name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if _, err := db.Exec(`ALTER TABLE syb_orders ADD COLUMN shop_name VARCHAR(500) NULL AFTER order_no`); err != nil {
|
||||
return fmt.Errorf("增加 syb_orders.shop_name 失败: %w", err)
|
||||
}
|
||||
}
|
||||
if _, err := db.Exec(`UPDATE syb_orders
|
||||
SET shop_name=NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(
|
||||
IF(JSON_VALID(syb_data),syb_data,'{}'),'$.stock.shopName'))),'')
|
||||
WHERE shop_name IS NULL`); err != nil {
|
||||
return fmt.Errorf("回填顺运宝店铺名失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkMySQLV11Shape(db *sql.DB) error {
|
||||
if err := checkMySQLVarcharColumn(db, "syb_orders", "shop_name", 500, true, "utf8mb4_0900_ai_ci", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkMySQLNullDefault(db, "syb_orders", "shop_name")
|
||||
}
|
||||
|
||||
// migrateMySQLV10 修复曾在 v8/v9 开发中间状态记录过版本的数据库。
|
||||
// 两版迁移均逐项检查结构,v8 回填只处理空值,因此可安全重放;
|
||||
// 历史 v8/v9 版本记录保持不变,完整自检通过后再追加 v10。
|
||||
@@ -564,7 +604,10 @@ func checkMySQLV10Shape(db *sql.DB) error {
|
||||
if err := checkMySQLV8Shape(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkMySQLV9Shape(db)
|
||||
if err := checkMySQLV9Shape(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateMySQLV9(db *sql.DB) error {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"cmautobuy/admin/config"
|
||||
"cmautobuy/admin/model"
|
||||
"cmautobuy/admin/spec"
|
||||
)
|
||||
|
||||
@@ -550,6 +551,40 @@ func TestMySQLMigrate_V10执行后未记版本可继续收敛(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMySQLMigrate_V11回填顺运宝店铺且可重放(t *testing.T) {
|
||||
db := openMySQLMigrationTestDB(t)
|
||||
defer db.Close()
|
||||
cleanMySQLTestSchema(t, db)
|
||||
defer cleanMySQLTestSchema(t, db)
|
||||
if err := MigrateMySQL(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mustExec(t, db, `DELETE FROM schema_migrations WHERE version=11`)
|
||||
mustExec(t, db, `ALTER TABLE syb_orders DROP COLUMN shop_name`)
|
||||
now := model.NowISO()
|
||||
mustExec(t, db, `INSERT INTO syb_orders
|
||||
(syb_id,order_no,title,product_spec,quantity,syb_data,created_at,updated_at) VALUES
|
||||
('S1','O1','商品','黑色,M',1,'{"stock":{"shopName":" 测试店铺 "}}',?,?),
|
||||
('S2','O2','商品','白色,L',1,'{not-json',?,?)`, now, now, now, now)
|
||||
if err := MigrateMySQL(db); err != nil {
|
||||
t.Fatalf("v10 升级 v11 失败: %v", err)
|
||||
}
|
||||
var shop sql.NullString
|
||||
if err := db.QueryRow(`SELECT shop_name FROM syb_orders WHERE syb_id='S1'`).Scan(&shop); err != nil || !shop.Valid || shop.String != "测试店铺" {
|
||||
t.Fatalf("MySQL 店铺回填错误: %+v err=%v", shop, err)
|
||||
}
|
||||
if err := db.QueryRow(`SELECT shop_name FROM syb_orders WHERE syb_id='S2'`).Scan(&shop); err != nil || shop.Valid {
|
||||
t.Fatalf("无效 JSON 应保持 NULL: %+v err=%v", shop, err)
|
||||
}
|
||||
if err := MigrateMySQL(db); err != nil {
|
||||
t.Fatalf("v11 重放失败: %v", err)
|
||||
}
|
||||
var count int
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE version=11`).Scan(&count); err != nil || count != 1 {
|
||||
t.Fatalf("v11 版本记录错误: count=%d err=%v", count, err)
|
||||
}
|
||||
}
|
||||
|
||||
func openMySQLMigrationTestDB(t *testing.T) *sql.DB {
|
||||
t.Helper()
|
||||
if os.Getenv("CMAUTOBUY_MYSQL_TEST") != "1" {
|
||||
|
||||
@@ -56,7 +56,7 @@ var sqliteMigrationTables = []sqliteMigrationTableSpec{
|
||||
{name: "shopee_products", columns: []string{"goods_id", "title", "shopee_status", "main_sku_code", "pdd_goods_url", "pdd_goods_id", "created_at", "updated_at"}, primaryKey: []string{"goods_id"}},
|
||||
{name: "pdd_products", columns: []string{"id", "goods_id", "url", "title", "shop_name", "skus_json", "collect_status", "collect_msg", "artifact_ref", "collected_at", "deleted_at", "created_at", "updated_at"}, primaryKey: []string{"id"}},
|
||||
{name: "shopee_skus", columns: []string{"sku_id", "goods_id", "spec_raw", "color", "size", "advice", "parse_ok", "sku_code", "is_manual", "created_at", "updated_at"}, primaryKey: []string{"sku_id"}},
|
||||
{name: "syb_orders", columns: []string{"syb_id", "order_no", "title", "shopee_goods_id", "shopee_sku_id", "product_spec", "quantity", "price_twd_cent", "image_url", "syb_data", "created_at", "updated_at"}, primaryKey: []string{"syb_id"}},
|
||||
{name: "syb_orders", columns: []string{"syb_id", "order_no", "shop_name", "title", "shopee_goods_id", "shopee_sku_id", "product_spec", "quantity", "price_twd_cent", "image_url", "syb_data", "created_at", "updated_at"}, primaryKey: []string{"syb_id"}},
|
||||
{name: "sku_mappings", columns: []string{"shopee_sku_id", "pdd_goods_id", "pdd_option_key", "pdd_options", "goods_id", "mapped_at", "mapped_by"}, primaryKey: []string{"shopee_sku_id", "pdd_goods_id"}},
|
||||
{name: "tasks", columns: []string{"task_id", "task_type", "status", "version", "priority", "assigned_client", "claimed_at", "syb_id", "order_no", "goods_id", "shopee_sku_id", "pdd_goods_url", "pdd_goods_id", "pdd_options", "quantity", "max_price_cent", "result_data", "error_code", "error_message", "finished_at", "created_at", "updated_at"}, primaryKey: []string{"task_id"}},
|
||||
{name: "task_claims", columns: []string{"task_id", "client_id", "claimed_at"}, primaryKey: []string{"task_id", "client_id"}},
|
||||
|
||||
+18
-10
@@ -274,11 +274,12 @@ func UpsertSybOrder(q Execer, o model.SybOrder) (created bool, err error) {
|
||||
}
|
||||
_, err = q.Exec(`
|
||||
INSERT INTO syb_orders
|
||||
(syb_id, order_no, title, product_spec, spec_key, shopee_goods_id,
|
||||
(syb_id, order_no, shop_name, title, product_spec, spec_key, shopee_goods_id,
|
||||
quantity, price_twd_cent, image_url, syb_data, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
order_no = VALUES(order_no),
|
||||
shop_name = VALUES(shop_name),
|
||||
title = VALUES(title),
|
||||
product_spec = VALUES(product_spec),
|
||||
spec_key = VALUES(spec_key),
|
||||
@@ -288,7 +289,7 @@ func UpsertSybOrder(q Execer, o model.SybOrder) (created bool, err error) {
|
||||
image_url = VALUES(image_url),
|
||||
syb_data = VALUES(syb_data),
|
||||
updated_at = VALUES(updated_at)`,
|
||||
o.SybID, o.OrderNo, o.Title, nullableText(o.ProductSpec), specKey, nullableText(o.ShopeeGoodsID),
|
||||
o.SybID, o.OrderNo, nullableText(o.ShopName), o.Title, nullableText(o.ProductSpec), specKey, nullableText(o.ShopeeGoodsID),
|
||||
o.Quantity, o.PriceTwdCent, nullableText(o.ImageURL),
|
||||
o.SybData, now, now)
|
||||
if err != nil {
|
||||
@@ -353,6 +354,7 @@ func nullableText(s string) any {
|
||||
// SybOrderFilter 是货运单列表页支持的筛选条件。
|
||||
type SybOrderFilter struct {
|
||||
Keyword string // 匹配订单号或商品标题
|
||||
Shop string // 店铺名模糊匹配;空字符串表示全部
|
||||
Stage string // 处理阶段;空字符串表示全部
|
||||
}
|
||||
|
||||
@@ -364,6 +366,10 @@ func sybOrderFilterClause(filter SybOrderFilter) (string, []any) {
|
||||
clauses = append(clauses, `(so.order_no LIKE ? ESCAPE '!' OR so.title LIKE ? ESCAPE '!')`)
|
||||
args = append(args, like, like)
|
||||
}
|
||||
if shop := strings.TrimSpace(filter.Shop); shop != "" {
|
||||
clauses = append(clauses, `so.shop_name LIKE ? ESCAPE '!'`)
|
||||
args = append(args, "%"+escapeLike(shop)+"%")
|
||||
}
|
||||
switch filter.Stage {
|
||||
case "spec_missing":
|
||||
clauses = append(clauses, `so.spec_key IS NULL`)
|
||||
@@ -405,19 +411,20 @@ type SybOrderContext struct {
|
||||
|
||||
func scanSybOrderContext(s rowScanner) (SybOrderContext, error) {
|
||||
var c SybOrderContext
|
||||
var title, productSpec, specKey, shopeeGoodsID, imageURL sql.NullString
|
||||
var shopName, title, productSpec, specKey, shopeeGoodsID, imageURL sql.NullString
|
||||
var priceCent sql.NullInt64
|
||||
var shopeeExists int
|
||||
var pddGoodsID, pddGoodsURL, collectStatus, collectMsg, skusJSON, pddUpdatedAt sql.NullString
|
||||
var mappingKey, mappingOptions sql.NullString
|
||||
var hasActiveTask int
|
||||
err := s.Scan(
|
||||
&c.Order.SybID, &c.Order.OrderNo, &title, &productSpec, &specKey, &shopeeGoodsID,
|
||||
&c.Order.SybID, &c.Order.OrderNo, &shopName, &title, &productSpec, &specKey, &shopeeGoodsID,
|
||||
&c.Order.Quantity, &priceCent, &imageURL, &c.Order.SybData,
|
||||
&c.Order.CreatedAt, &c.Order.UpdatedAt, &shopeeExists,
|
||||
&pddGoodsID, &pddGoodsURL, &collectStatus, &collectMsg, &skusJSON, &pddUpdatedAt,
|
||||
&mappingKey, &mappingOptions, &hasActiveTask,
|
||||
)
|
||||
c.Order.ShopName = shopName.String
|
||||
c.Order.Title = title.String
|
||||
c.Order.ProductSpec = productSpec.String
|
||||
c.Order.SpecKey = specKey.String
|
||||
@@ -441,7 +448,7 @@ func scanSybOrderContext(s rowScanner) (SybOrderContext, error) {
|
||||
func ListSybOrderContexts(q Execer, filter SybOrderFilter, limit, offset int) ([]SybOrderContext, error) {
|
||||
where, args := sybOrderFilterClause(filter)
|
||||
query := `
|
||||
SELECT so.syb_id, so.order_no, so.title, so.product_spec, so.spec_key,
|
||||
SELECT so.syb_id, so.order_no, so.shop_name, so.title, so.product_spec, so.spec_key,
|
||||
so.shopee_goods_id, so.quantity,
|
||||
so.price_twd_cent, so.image_url, so.syb_data, so.created_at, so.updated_at,
|
||||
CASE WHEN sp.goods_id IS NULL THEN 0 ELSE 1 END,
|
||||
@@ -475,7 +482,7 @@ func ListSybOrderContexts(q Execer, filter SybOrderFilter, limit, offset int) ([
|
||||
// GetSybOrderContext 按明细 ID 读取一条处理上下文。
|
||||
func GetSybOrderContext(q Execer, sybID string) (*SybOrderContext, error) {
|
||||
row, err := scanSybOrderContext(q.QueryRow(`
|
||||
SELECT so.syb_id, so.order_no, so.title, so.product_spec, so.spec_key,
|
||||
SELECT so.syb_id, so.order_no, so.shop_name, so.title, so.product_spec, so.spec_key,
|
||||
so.shopee_goods_id, so.quantity,
|
||||
so.price_twd_cent, so.image_url, so.syb_data, so.created_at, so.updated_at,
|
||||
CASE WHEN sp.goods_id IS NULL THEN 0 ELSE 1 END,
|
||||
@@ -498,7 +505,7 @@ func GetSybOrderContext(q Execer, sybID string) (*SybOrderContext, error) {
|
||||
func ListSybOrders(q Execer, filter SybOrderFilter, limit, offset int) ([]model.SybOrder, error) {
|
||||
where, args := sybOrderFilterClause(filter)
|
||||
sqlText := `
|
||||
SELECT so.syb_id, so.order_no, so.title, so.product_spec, so.spec_key, so.shopee_goods_id,
|
||||
SELECT so.syb_id, so.order_no, so.shop_name, so.title, so.product_spec, so.spec_key, so.shopee_goods_id,
|
||||
so.quantity, so.price_twd_cent, so.image_url, so.syb_data, so.created_at, so.updated_at` +
|
||||
sybOrderContextFrom + where + `
|
||||
ORDER BY so.updated_at DESC, so.syb_id DESC LIMIT ? OFFSET ?`
|
||||
@@ -513,14 +520,15 @@ func ListSybOrders(q Execer, filter SybOrderFilter, limit, offset int) ([]model.
|
||||
var list []model.SybOrder
|
||||
for rows.Next() {
|
||||
var o model.SybOrder
|
||||
var title, productSpec, specKey, shopeeGoodsID, imageURL sql.NullString
|
||||
var shopName, title, productSpec, specKey, shopeeGoodsID, imageURL sql.NullString
|
||||
var priceCent sql.NullInt64
|
||||
if err := rows.Scan(
|
||||
&o.SybID, &o.OrderNo, &title, &productSpec, &specKey, &shopeeGoodsID,
|
||||
&o.SybID, &o.OrderNo, &shopName, &title, &productSpec, &specKey, &shopeeGoodsID,
|
||||
&o.Quantity, &priceCent, &imageURL, &o.SybData, &o.CreatedAt, &o.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("读取顺运宝货运单列表失败: %w", err)
|
||||
}
|
||||
o.ShopName = shopName.String
|
||||
o.Title = title.String
|
||||
o.ProductSpec = productSpec.String
|
||||
o.SpecKey = specKey.String
|
||||
|
||||
@@ -346,17 +346,17 @@ func TestInterruptRunningSybSyncRuns_只中断未完成记录(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSybOrders_关键字筛选订单号和标题(t *testing.T) {
|
||||
func TestListSybOrders_订单标题店铺组合筛选(t *testing.T) {
|
||||
db := newSybTestDB(t)
|
||||
mustUpsert := func(sybID, orderNo, title string) {
|
||||
mustUpsert := func(sybID, orderNo, shop, title string) {
|
||||
if _, err := UpsertSybOrder(db, model.SybOrder{
|
||||
SybID: sybID, OrderNo: orderNo, Title: title, Quantity: 1, SybData: "{}",
|
||||
SybID: sybID, OrderNo: orderNo, ShopName: shop, Title: title, Quantity: 1, SybData: "{}",
|
||||
}); err != nil {
|
||||
t.Fatalf("写入 %s 失败: %v", sybID, err)
|
||||
}
|
||||
}
|
||||
mustUpsert("SYB-A", "260728AAA", "纯棉上衣")
|
||||
mustUpsert("SYB-B", "260728BBB", "牛仔裤")
|
||||
mustUpsert("SYB-A", "260728AAA", "台北服饰旗舰店", "纯棉上衣")
|
||||
mustUpsert("SYB-B", "260728BBB", "台南生活馆", "牛仔裤")
|
||||
|
||||
rows, err := ListSybOrders(db, SybOrderFilter{Keyword: "AAA"}, 20, 0)
|
||||
if err != nil {
|
||||
@@ -373,4 +373,13 @@ func TestListSybOrders_关键字筛选订单号和标题(t *testing.T) {
|
||||
if len(rows2) != 1 || rows2[0].SybID != "SYB-B" {
|
||||
t.Fatalf("按标题筛选应该只查到 SYB-B,实际 %+v", rows2)
|
||||
}
|
||||
|
||||
rows3, err := ListSybOrders(db, SybOrderFilter{Shop: " 服饰 "}, 20, 0)
|
||||
if err != nil || len(rows3) != 1 || rows3[0].SybID != "SYB-A" || rows3[0].ShopName != "台北服饰旗舰店" {
|
||||
t.Fatalf("按店铺模糊筛选错误:rows=%+v err=%v", rows3, err)
|
||||
}
|
||||
count, err := CountSybOrders(db, SybOrderFilter{Keyword: "牛仔", Shop: "台南"})
|
||||
if err != nil || count != 1 {
|
||||
t.Fatalf("标题与店铺组合 COUNT 错误:count=%d err=%v", count, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user