feat: 展示顺运宝观测规格 (#140)

This commit is contained in:
chengma
2026-08-11 10:46:11 +08:00
parent 9181329917
commit c05c4fafdb
8 changed files with 199 additions and 3 deletions
+34 -2
View File
@@ -5,10 +5,12 @@ package service
import (
"database/sql"
"fmt"
"strings"
"cmautobuy/admin/model"
"cmautobuy/admin/repository"
"cmautobuy/admin/spec"
)
// ShopeeProductView 是列表页一行要显示的全部内容,全部已经是字符串,
@@ -215,8 +217,16 @@ type ShopeeProductDetail struct {
CollectMsg string
CanCollect bool
SKUs []ShopeeSpecView
PendingCount int
SKUs []ShopeeSpecView
PendingCount int
SybObservations []SybObservationView
}
// SybObservationView 是详情页只读的顺运宝历史观测,不是正式 SKU。
type SybObservationView struct {
SpecRaw, PriceText, ImageURL, LastObservedAt string
OrderCount, TotalQuantity int
MatchedSKUID, Color, Size, MatchText string
}
// GetShopeeProductDetail 读一个蝦皮商品的详情(商品信息 + 完整规格表)。
@@ -287,5 +297,27 @@ func GetShopeeProductDetail(db *sql.DB, goodsID string) (*ShopeeProductDetail, e
}
d.SKUs = append(d.SKUs, row)
}
observations, err := repository.ListSybSpecObservations(db, goodsID)
if err != nil {
return nil, err
}
formalByKey := map[string][]model.ShopeeSKU{}
for _, sk := range skus {
if key, keyErr := spec.SpecKey(sk.SpecRaw); keyErr == nil {
formalByKey[key] = append(formalByKey[key], sk)
}
}
for _, observation := range observations {
view := SybObservationView{SpecRaw: observation.SpecRaw, PriceText: fmt.Sprintf("NT$%.2f", float64(observation.LatestPriceCent)/100), ImageURL: observation.LatestImageURL, OrderCount: observation.OrderCount, TotalQuantity: observation.TotalQuantity, LastObservedAt: formatLocalTime(observation.LastObservedAt), MatchText: "待目录补全"}
if matches := formalByKey[observation.SpecKey]; len(matches) == 1 {
view.MatchedSKUID = matches[0].SKUID
view.Color = matches[0].Color
view.Size = matches[0].Size
view.MatchText = "唯一匹配"
} else if len(matches) > 1 {
view.MatchText = "多条匹配,待人工确认"
}
d.SybObservations = append(d.SybObservations, view)
}
return d, nil
}
+64
View File
@@ -36,6 +36,70 @@ func setShopeePddLink(t *testing.T, db *sql.DB, goodsID, pddGoodsID, pddURL stri
}
}
func TestGetShopeeProductDetail_汇总顺运宝观测且唯一匹配正式SKU(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "S-OBS", "观测商品")
seedShopeeSKU(t, db, "SKU-OBS", "S-OBS", "黑色,M", "黑色", "M", "", true)
now1, now2 := "2026-08-10T00:00:00.000000000Z", "2026-08-11T00:00:00.000000000Z"
for _, row := range []struct {
id string
qty int
price int64
image, at string
}{
{"OBS-1", 2, 23900, "https://example.invalid/old.jpg", now1},
{"OBS-2", 3, 25900, "https://example.invalid/new.jpg", now2},
} {
_, err := db.Exec(`INSERT INTO syb_orders(syb_id,order_no,title,product_spec,spec_key,shopee_goods_id,quantity,price_twd_cent,image_url,syb_data,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,'{}',?,?)`, row.id, "ORDER-"+row.id, "观测商品", "黑色,M", "黑色,M", "S-OBS", row.qty, row.price, row.image, row.at, row.at)
if err != nil {
t.Fatal(err)
}
}
detail, err := GetShopeeProductDetail(db, "S-OBS")
if err != nil {
t.Fatal(err)
}
if len(detail.SybObservations) != 1 {
t.Fatalf("观测规格数=%d", len(detail.SybObservations))
}
got := detail.SybObservations[0]
if got.OrderCount != 2 || got.TotalQuantity != 5 || got.PriceText != "NT$259.00" || got.ImageURL != "https://example.invalid/new.jpg" {
t.Fatalf("汇总错误:%+v", got)
}
if got.MatchedSKUID != "SKU-OBS" || got.MatchText != "唯一匹配" {
t.Fatalf("正式 SKU 匹配错误:%+v", got)
}
// 同一 syb_id 只是更新,不会产生第三个观测订单。
if _, err := db.Exec(`UPDATE syb_orders SET quantity=4 WHERE syb_id='OBS-2'`); err != nil {
t.Fatal(err)
}
detail, _ = GetShopeeProductDetail(db, "S-OBS")
if detail.SybObservations[0].OrderCount != 2 || detail.SybObservations[0].TotalQuantity != 6 {
t.Fatalf("重放后统计错误:%+v", detail.SybObservations[0])
}
}
func TestGetShopeeProductDetail_多条正式SKU不猜测(t *testing.T) {
db := newTestDB(t)
seedShopeeProduct(t, db, "S-MULTI", "多匹配")
seedShopeeSKU(t, db, "SKU-1", "S-MULTI", "黑色,M", "黑色", "M", "", true)
seedShopeeSKU(t, db, "SKU-2", "S-MULTI", "黑色,M", "黑色", "M", "", true)
now := "2026-08-11T00:00:00.000000000Z"
_, err := db.Exec(`INSERT INTO syb_orders(syb_id,order_no,title,product_spec,spec_key,shopee_goods_id,quantity,price_twd_cent,syb_data,created_at,updated_at) VALUES('O-1','ORDER','多匹配','黑色,M','黑色,M','S-MULTI',1,100,'{}',?,?)`, now, now)
if err != nil {
t.Fatal(err)
}
detail, err := GetShopeeProductDetail(db, "S-MULTI")
if err != nil {
t.Fatal(err)
}
got := detail.SybObservations[0]
if got.MatchedSKUID != "" || got.MatchText != "多条匹配,待人工确认" {
t.Fatalf("不应猜测:%+v", got)
}
}
// ── 列表:商品级聚合 ──────────────────────────────────
func TestListShopeeProducts_商品级一行(t *testing.T) {