fix: 兼容档口货号括号与前导零 (#273)
This commit is contained in:
@@ -5,11 +5,11 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
"cmautobuy/admin/repository"
|
||||
@@ -419,19 +419,103 @@ func innerCodeStallMatches(stall string, item syb.DetailItem) bool {
|
||||
if strings.Contains(blob, stall) {
|
||||
return true
|
||||
}
|
||||
name, article, hasArticle := strings.Cut(stall, "#")
|
||||
name, article, hasArticle := splitInnerCodeStall(stall)
|
||||
if !hasArticle {
|
||||
return false
|
||||
}
|
||||
name, article = strings.TrimSpace(name), strings.TrimSpace(article)
|
||||
if article != "" {
|
||||
if strings.Contains(blob, "#"+article) || strings.HasPrefix(item.ProductSpec, article+" ") || strings.HasPrefix(item.ProductSpec, article+",") {
|
||||
if article == "" {
|
||||
return name != "" && (strings.Contains(sku, name) || strings.Contains(variation, name))
|
||||
}
|
||||
|
||||
// 纯数字货号容易在不同档口重复。只有候选也包含同一档口名称时,
|
||||
// 才允许把 067 与 67 视为同一个货号;不能只凭短数字自动选商品。
|
||||
if isInnerCodeNumericArticle(article) {
|
||||
nameMatches := name != "" && (strings.Contains(sku, name) || strings.Contains(variation, name))
|
||||
if !nameMatches {
|
||||
return false
|
||||
}
|
||||
return innerCodeTextHasNumericArticle(sku, article) ||
|
||||
innerCodeTextHasNumericArticle(variation, article) ||
|
||||
innerCodeTextHasNumericArticle(item.ProductSpec, article)
|
||||
}
|
||||
|
||||
return innerCodeTextHasExactArticle(sku, article) ||
|
||||
innerCodeTextHasExactArticle(variation, article) ||
|
||||
innerCodeTextHasExactArticle(item.ProductSpec, article)
|
||||
}
|
||||
|
||||
// splitInnerCodeStall 从“档口名称#货号”取最后一个 #,避免档口名称本身含 # 时截错。
|
||||
func splitInnerCodeStall(stall string) (name, article string, ok bool) {
|
||||
stall = strings.TrimSpace(stall)
|
||||
separator := strings.LastIndex(stall, "#")
|
||||
if separator < 0 {
|
||||
return stall, "", false
|
||||
}
|
||||
return strings.TrimSpace(stall[:separator]), strings.TrimSpace(stall[separator+1:]), true
|
||||
}
|
||||
|
||||
func isInnerCodeNumericArticle(article string) bool {
|
||||
if article == "" {
|
||||
return false
|
||||
}
|
||||
for _, char := range article {
|
||||
if !unicode.IsDigit(char) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
pattern := regexp.MustCompile(`(?:^|[#\-_/\s])` + regexp.QuoteMeta(article) + `(?:$|[#\-_/,\s])`)
|
||||
return pattern.MatchString(sku) || pattern.MatchString(variation)
|
||||
|
||||
func innerCodeTextHasNumericArticle(text, article string) bool {
|
||||
target := normalizeInnerCodeNumericArticle(article)
|
||||
for _, token := range innerCodeArticleTokens(text) {
|
||||
if isInnerCodeNumericArticle(token) && normalizeInnerCodeNumericArticle(token) == target {
|
||||
return true
|
||||
}
|
||||
return name != "" && (strings.Contains(sku, name) || strings.Contains(variation, name))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeInnerCodeNumericArticle(article string) string {
|
||||
normalized := strings.TrimLeft(article, "0")
|
||||
if normalized == "" {
|
||||
return "0"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func innerCodeTextHasExactArticle(text, article string) bool {
|
||||
for _, token := range innerCodeArticleTokens(text) {
|
||||
if token == article {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// innerCodeArticleTokens 只把连续字母或数字视为货号候选。
|
||||
// `【】`、`#`、横线、空格等标点自然成为边界,因此能识别 `067【档口】`,
|
||||
// 又不会把 `PDD256437` 中间的数字误认为独立货号。
|
||||
func innerCodeArticleTokens(text string) []string {
|
||||
tokens := make([]string, 0)
|
||||
start := -1
|
||||
runes := []rune(text)
|
||||
for index, char := range runes {
|
||||
if unicode.IsLetter(char) || unicode.IsDigit(char) {
|
||||
if start < 0 {
|
||||
start = index
|
||||
}
|
||||
continue
|
||||
}
|
||||
if start >= 0 {
|
||||
tokens = append(tokens, string(runes[start:index]))
|
||||
start = -1
|
||||
}
|
||||
}
|
||||
if start >= 0 {
|
||||
tokens = append(tokens, string(runes[start:]))
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func innerCodeRawText(value any) string {
|
||||
|
||||
@@ -223,6 +223,75 @@ func TestMatchInnerCodeItemsWithEvidence_重复和证据冲突均阻断(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestInnerCodeStallMatches_中文括号货号与数字前导零(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
stall string
|
||||
item syb.DetailItem
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "067在中文括号前为独立货号",
|
||||
stall: "将客大码网批#067",
|
||||
item: innerCodeTestItem(1, "黑色(有口袋),2XL【建議67.5-82.5公斤】", map[string]any{
|
||||
"sku": "067【将客大码网批】-黑色(有口袋)-2XL",
|
||||
}),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "同档口67与067等价",
|
||||
stall: "将客大码网批#067",
|
||||
item: innerCodeTestItem(2, "黑色,2XL", map[string]any{
|
||||
"variationSku": "汽配-将客大码网批#67#(&19)",
|
||||
}),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "同货号不同档口不匹配",
|
||||
stall: "将客大码网批#067",
|
||||
item: innerCodeTestItem(3, "黑色,2XL", map[string]any{
|
||||
"sku": "067【其他档口】-黑色-2XL", "variationSku": "汽配-其他档口#67",
|
||||
}),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "非数字货号不做类似转换",
|
||||
stall: "A档#A01",
|
||||
item: innerCodeTestItem(4, "黑色,M", map[string]any{"sku": "A档#A1"}),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "非数字货号精确匹配",
|
||||
stall: "A档#A01",
|
||||
item: innerCodeTestItem(5, "黑色,M", map[string]any{"sku": "A档#A01"}),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if got := innerCodeStallMatches(test.stall, test.item); got != test.want {
|
||||
t.Fatalf("innerCodeStallMatches(%q)=%t,期望 %t", test.stall, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanInnerCodeRows_将客档口067唯一匹配生产样例(t *testing.T) {
|
||||
record := model.InnerCodeRecord{ID: 255, OrderNumber: "260814A21AAB1G",
|
||||
Stall: "将客大码网批#067", SpecRaw: "黑色(有口袋),2XL【建議67.5-82.5公斤】",
|
||||
InnerCode: "DK-255", SourceDuplicateCount: 1}
|
||||
item := innerCodeTestItem(147048059, record.SpecRaw, map[string]any{
|
||||
"sku": "067【将客大码网批】-黑色(有口袋)-2XL",
|
||||
"variationSku": "汽配-将客大码网批#67#(&19)",
|
||||
})
|
||||
plans := planInnerCodeRows([]model.InnerCodeRecord{record},
|
||||
map[string][]int64{record.OrderNumber: {76159471}},
|
||||
map[int64]syb.StockDetail{76159471: {ID: 76159471, Details: []syb.DetailItem{item}}})
|
||||
if len(plans) != 1 || plans[0].Status != model.InnerCodeReady || plans[0].DetailID != item.ID {
|
||||
t.Fatalf("将客档口记录未生成可回写计划: %+v", plans)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanInnerCodeRows_多件数量一致放行且其他异常继续阻断(t *testing.T) {
|
||||
records := []model.InnerCodeRecord{
|
||||
{ID: 1, OrderNumber: "DUP", SpecRaw: "黑色,M", InnerCode: "DK1,DK2", SourceDuplicateCount: 2},
|
||||
|
||||
Reference in New Issue
Block a user