feat: 增加规格匹配建议与决策审计 (#90)
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
"cmautobuy/admin/repository"
|
||||
@@ -13,19 +14,23 @@ import (
|
||||
|
||||
// PddOptionChoice 是采集结果中的一个可购买规格组合。
|
||||
type PddOptionChoice struct {
|
||||
Key string
|
||||
OptionsJSON string
|
||||
Label string
|
||||
PriceText string
|
||||
PriceCent int64
|
||||
HasPrice bool
|
||||
Selected bool
|
||||
Key string
|
||||
OptionsJSON string
|
||||
Label string
|
||||
PriceText string
|
||||
PriceCent int64
|
||||
HasPrice bool
|
||||
Selected bool
|
||||
Options map[string]string
|
||||
Recommended bool
|
||||
RecommendationReason string
|
||||
MatchLevel string
|
||||
}
|
||||
|
||||
func pddOptionChoices(raw string) ([]PddOptionChoice, []string, error) {
|
||||
func pddOptionChoices(raw string) ([]PddOptionChoice, []string, []string, error) {
|
||||
collected, err := parseCollected(raw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keys, names := dimensionOrder(collected)
|
||||
choices := make([]PddOptionChoice, 0, len(collected.SKUs))
|
||||
@@ -36,7 +41,7 @@ func pddOptionChoices(raw string) ([]PddOptionChoice, []string, error) {
|
||||
}
|
||||
key, err := OptionKey(sku.Options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if seen[key] {
|
||||
continue
|
||||
@@ -52,18 +57,18 @@ func pddOptionChoices(raw string) ([]PddOptionChoice, []string, error) {
|
||||
}
|
||||
choice := PddOptionChoice{
|
||||
Key: key, OptionsJSON: key, Label: strings.Join(parts, " / "),
|
||||
PriceText: formatPriceCent(sku.PriceCent),
|
||||
PriceText: formatPriceCent(sku.PriceCent), Options: sku.Options,
|
||||
}
|
||||
if sku.PriceCent != nil && *sku.PriceCent > 0 {
|
||||
choice.PriceCent, choice.HasPrice = *sku.PriceCent, true
|
||||
}
|
||||
choices = append(choices, choice)
|
||||
}
|
||||
return choices, names, nil
|
||||
return choices, keys, names, nil
|
||||
}
|
||||
|
||||
func findPddChoice(raw, key string) (*PddOptionChoice, error) {
|
||||
choices, _, err := pddOptionChoices(raw)
|
||||
choices, _, _, err := pddOptionChoices(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -77,7 +82,7 @@ func findPddChoice(raw, key string) (*PddOptionChoice, error) {
|
||||
}
|
||||
|
||||
// SaveSybMapping 保存顺运宝商品规格到当前 PDD 商品的可复用映射。
|
||||
func SaveSybMapping(db *sql.DB, sybID, optionKey, operator string) error {
|
||||
func SaveSybMapping(db *sql.DB, sybID, optionKey, operator string, expectedVersions ...string) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("开始保存规格映射事务失败: %w", err)
|
||||
@@ -90,6 +95,13 @@ func SaveSybMapping(db *sql.DB, sybID, optionKey, operator string) error {
|
||||
if context == nil {
|
||||
return fmt.Errorf("顺运宝明细不存在")
|
||||
}
|
||||
expectedContextVersion := mappingContextVersion(*context)
|
||||
if len(expectedVersions) > 0 {
|
||||
expectedContextVersion = expectedVersions[0]
|
||||
}
|
||||
if expectedContextVersion == "" || mappingContextVersion(*context) != expectedContextVersion {
|
||||
return fmt.Errorf("数据或匹配规则已变化,请刷新后重新核对")
|
||||
}
|
||||
key, err := spec.SpecKey(context.Order.ProductSpec)
|
||||
if err != nil || context.Order.SpecKey == "" {
|
||||
return fmt.Errorf("顺运宝未提供有效规格,不能保存映射")
|
||||
@@ -108,6 +120,14 @@ func SaveSybMapping(db *sql.DB, sybID, optionKey, operator string) error {
|
||||
if choice == nil {
|
||||
return fmt.Errorf("所选 PDD 规格已不存在或不可购买,请刷新后重试")
|
||||
}
|
||||
choices, dimensionKeys, dimensionNames, err := pddOptionChoices(context.PddSkusJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取 PDD 规格失败: %w", err)
|
||||
}
|
||||
match := rankSpecChoices(context.Order.ProductSpec, choices, dimensionKeys, dimensionNames)
|
||||
if utf8.RuneCountInString(choice.Key) > 191 || utf8.RuneCountInString(match.SuggestedOptionKey) > 191 || utf8.RuneCountInString(SpecMatchRulesVersion) > 32 {
|
||||
return fmt.Errorf("规格选项键或规则版本超过数据库列宽,未保存")
|
||||
}
|
||||
if err := repository.UpsertSpecMapping(tx, model.SpecMapping{
|
||||
ShopeeGoodsID: context.Order.ShopeeGoodsID, SpecKey: key,
|
||||
SpecRaw: context.Order.ProductSpec, PddGoodsID: context.PddGoodsID,
|
||||
@@ -116,6 +136,14 @@ func SaveSybMapping(db *sql.DB, sybID, optionKey, operator string) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := repository.InsertSpecMappingDecision(tx, model.SpecMappingDecision{
|
||||
ShopeeGoodsID: context.Order.ShopeeGoodsID, SpecKey: key, PddGoodsID: context.PddGoodsID,
|
||||
RulesVersion: SpecMatchRulesVersion, SuggestedOptionKey: match.SuggestedOptionKey,
|
||||
ChosenOptionKey: choice.Key, Accepted: match.SuggestedOptionKey != "" && match.SuggestedOptionKey == choice.Key,
|
||||
DecidedBy: strings.TrimSpace(operator), DecidedAt: model.NowISO(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user