图搜采集成功并自动关联后,拉取该虾皮商品的完整颜色尺码清单合并进档案, 再触发一次匹配。此后同一虾皮商品的其它颜色 SYB 订单无需再采集、再点匹配。 - 新增 shopeespec 叶子包,凭据只从 GOAUTO_ERPGO_BASE_URL/GOAUTO_ERPGO_APIKEY 读取;错误文本不携带 URL,避免 apikey 流进日志。 - 写入档案前经 sybspec.StripAnnotations 剥离 【...】,与 SYB 明细同源, 否则 confirmedMappings 查不到、映射全部落空。 - 标记记录同步时对着哪个 PDD 商品(spec_sync_pdd_product_id),不是布尔值, 重新关联时自动失效。 - 已完整同步的商品再次图搜时返回 IMAGE_SEARCH_SPEC_SYNCED。 - 迁移只加列不回填:既有档案仍是从 SYB 明细增量累积的,不能假装已同步。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
113 lines
4.1 KiB
Go
113 lines
4.1 KiB
Go
package task
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"time"
|
|
|
|
"go-admin/app/goauto/models"
|
|
"go-admin/app/goauto/shopeeproduct"
|
|
"go-admin/app/goauto/shopeespec"
|
|
"go-admin/app/goauto/sybspec"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// syncShopeeSpecs pulls the Shopee product's complete colour and size list and
|
|
// folds it into the archive, so every future SYB order already has a value to
|
|
// map (#290).
|
|
//
|
|
// `[必须]` 接口返回的是虾皮原文(`香芋紫 【雙梅花】純棉`),而 SYB 明细经
|
|
// sybspec.Parse 剥离 【...】 后是 `香芋紫 純棉`。档案里的键必须与 SYB 送来的
|
|
// 一致,否则 confirmedMappings 查不到,映射全部落空、等于白做。因此写入前要
|
|
// 走一遍同样的解析。
|
|
//
|
|
// `[必须]` 失败只记录不冒泡。采集结果已经提交完成,不能因为这个可选增强让
|
|
// SubmitResult 失败、让 Agent 以为采集没成功。接口未配置时直接跳过。
|
|
func (service *Service) syncShopeeSpecs(ctx context.Context, shopeeProductID uint64, pddProductID uint64) {
|
|
var product models.ShopeeProduct
|
|
if err := service.DB.WithContext(ctx).First(&product, shopeeProductID).Error; err != nil {
|
|
return
|
|
}
|
|
spec, err := shopeespec.NewClient().Fetch(ctx, product.ShopeeItemID)
|
|
if err != nil {
|
|
if err != shopeespec.ErrNotConfigured {
|
|
log.Printf("shopee spec sync failed for shopee product %d: %v", shopeeProductID, err)
|
|
}
|
|
return
|
|
}
|
|
incoming := incomingDimensions(spec)
|
|
if len(incoming) == 0 {
|
|
return
|
|
}
|
|
if err := service.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var current models.ShopeeProduct
|
|
if err := tx.First(¤t, shopeeProductID).Error; err != nil {
|
|
return err
|
|
}
|
|
// 关联在拉取期间被改动时放弃:映射要对着当前关联的 PDD 商品建立。
|
|
if current.PDDProductID == nil || *current.PDDProductID != pddProductID {
|
|
return nil
|
|
}
|
|
existing, err := shopeeproduct.Unmarshal(current.SpecsJSON)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Merge 保留既有映射,只补新值(见 shopeeproduct.Merge)。
|
|
merged := shopeeproduct.Merge(existing, incoming)
|
|
if err := shopeeproduct.Validate(merged); err != nil {
|
|
return err
|
|
}
|
|
specsJSON, err := shopeeproduct.Marshal(merged)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
now := time.Now().UTC()
|
|
return tx.Model(&models.ShopeeProduct{}).Where("id = ?", shopeeProductID).
|
|
Updates(map[string]any{
|
|
"specs_json": specsJSON,
|
|
"spec_sync_pdd_product_id": pddProductID,
|
|
"spec_sync_at": now,
|
|
}).Error
|
|
}); err != nil {
|
|
log.Printf("shopee spec sync write failed for shopee product %d: %v", shopeeProductID, err)
|
|
}
|
|
}
|
|
|
|
// incomingDimensions converts the platform's raw values into archive values,
|
|
// applying exactly the parsing SYB details go through.
|
|
func incomingDimensions(spec shopeespec.Spec) []shopeeproduct.SpecDimension {
|
|
colors := parsedValues(spec.Colors, sybspec.StripAnnotations)
|
|
sizes := parsedValues(spec.Sizes, sybspec.StripAnnotations)
|
|
dimensions := make([]shopeeproduct.SpecDimension, 0, 2)
|
|
if len(colors) > 0 {
|
|
dimensions = append(dimensions, shopeeproduct.SpecDimension{Name: "颜色", Role: shopeeproduct.RoleColor, Values: colors})
|
|
}
|
|
if len(sizes) > 0 {
|
|
dimensions = append(dimensions, shopeeproduct.SpecDimension{Name: "尺码", Role: shopeeproduct.RoleSize, Values: sizes})
|
|
}
|
|
return dimensions
|
|
}
|
|
|
|
func parsedValues(raws []string, parse func(string) string) []shopeeproduct.SpecValue {
|
|
values := make([]shopeeproduct.SpecValue, 0, len(raws))
|
|
seen := map[string]bool{}
|
|
for _, raw := range raws {
|
|
name := parse(raw)
|
|
if name == "" || seen[name] {
|
|
// 空值跳过;重名说明两个虾皮规格剥离后塌缩,这里只写一个值,
|
|
// 具体的歧义由 #289 的塌缩检测在采购侧拦截。
|
|
continue
|
|
}
|
|
seen[name] = true
|
|
values = append(values, shopeeproduct.SpecValue{Name: name, Source: shopeeproduct.ValueSourceImport})
|
|
}
|
|
return values
|
|
}
|
|
|
|
// specSyncCoversCurrentLink reports whether the recorded spec sync still
|
|
// applies to the product's current PDD link (#290).
|
|
func specSyncCoversCurrentLink(syncedWith, current *uint64) bool {
|
|
return syncedWith != nil && current != nil && *syncedWith == *current
|
|
}
|