fix(server): 消歧后的新键补进档案 (#301)

resyncShopeeProductKeys 改了兄弟明细的 target_color,却没把新键写进档案;只有被
直接重解析的那一条走了 mergeParsedSpec。

线上 2026-09-17 的后果:明细已是 `黑色【長袖】`,档案里还是旧的 `黑色`,采购查
映射查不到,明细永远停在「规格待匹配」;而采购员点「一键匹配」匹到的是档案里
剩下的旧键,看起来成功了却没有任何明细在用它。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
This commit is contained in:
QiuSW
2026-09-17 09:42:15 +08:00
co-authored by Claude Opus 5
parent c1bd39496d
commit 0e41000f62
2 changed files with 84 additions and 0 deletions
@@ -56,6 +56,13 @@ func resyncShopeeProductKeys(tx *gorm.DB, shopeeProductID uint64, incomingRaw st
Updates(map[string]any{"target_color": keys.Color, "target_size": keys.Size}).Error; err != nil {
return sybspec.ParseResult{}, err
}
// `[必须]` 改了明细的键就必须把新键补进档案。映射是按档案里的值建的,
// 档案没有 `黑色【長袖】` 这个条目,采购查映射就查不到,明细永远停在
// 「规格待匹配」——而采购员点「一键匹配」匹到的是档案里剩下的旧键
// `黑色`,看起来成功了却没有任何明细在用它。
if err := mergeParsedSpec(tx, shopeeProductID, keys); err != nil {
return sybspec.ParseResult{}, err
}
}
return resolved[incomingRaw], nil
}
@@ -0,0 +1,77 @@
package sybimport_test
import (
"context"
"encoding/json"
"testing"
"go-admin/app/goauto/models"
"go-admin/app/goauto/shopeeproduct"
"go-admin/app/goauto/sybimport"
)
func sleeveDetail(id uint64, spec string) sybimport.DetailInput {
raw, _ := json.Marshal(map[string]any{
"id": id, "productQty": 1, "productPrice": 100.0, "productThumb": 1,
"productTitle": "襯衫", "productSpec": spec, "productId": 24377106095,
})
return sybimport.DetailInput{
ID: id, ProductID: 24377106095, ProductQty: 1, ProductPrice: 100.0,
ProductSpec: spec, ProductTitle: "襯衫", ProductThumb: 1, Raw: raw,
}
}
// `[必须]` 消歧改了明细的键,就必须把新键补进档案。映射是按档案里的值建的,
// 档案没有 `黑色【長袖】` 这个条目,采购查映射就查不到,明细永远停在「规格待匹配」;
// 而采购员点「一键匹配」匹到的是档案里剩下的旧键 `黑色`,看起来成功了却没有任何
// 明细在用它——线上 2026-09-17 正是这样(#301)。
func TestDisambiguatedKeysReachTheArchive(t *testing.T) {
db := openTestDB(t)
order := realOrder()
if _, err := sybimport.ApplyDetail(context.Background(), db, order,
sleeveDetail(900001, "黑色【短袖】,M 【建議42.5-47.5公斤】")); err != nil {
t.Fatal(err)
}
second, err := sybimport.ApplyDetail(context.Background(), db, order,
sleeveDetail(900002, "黑色【長袖】,5XL 【建議72.5-77.5公斤】"))
if err != nil {
t.Fatal(err)
}
if second.SYBProduct.ShopeeProductID == nil {
t.Fatal("未关联虾皮商品")
}
var product models.ShopeeProduct
if err := db.First(&product, *second.SYBProduct.ShopeeProductID).Error; err != nil {
t.Fatal(err)
}
specs, err := shopeeproduct.Unmarshal(product.SpecsJSON)
if err != nil {
t.Fatal(err)
}
archived := map[string]bool{}
for _, dimension := range specs {
for _, value := range dimension.Values {
archived[value.Name] = true
}
}
// 两个明细的键都必须能在档案里找到,否则映射无从建立。
for _, want := range []string{"黑色【短袖】", "黑色【長袖】"} {
if !archived[want] {
t.Fatalf("档案缺少键 %q,现有键: %v", want, archived)
}
}
// 明细自己的键也必须是拆开后的形式。
var rows []models.SYBProduct
if err := db.Where("shopee_product_id = ?", *second.SYBProduct.ShopeeProductID).Find(&rows).Error; err != nil {
t.Fatal(err)
}
for _, row := range rows {
if row.TargetColor != "黑色【短袖】" && row.TargetColor != "黑色【長袖】" {
t.Fatalf("明细 %d 的键未消歧: %q", row.ID, row.TargetColor)
}
}
}