fix: 兼容相同顺运宝明细逐件回写 (#289)

This commit is contained in:
chengma
2026-08-22 11:46:23 +08:00
parent de10fb6cf4
commit 18ad85d3cb
7 changed files with 460 additions and 32 deletions
+110
View File
@@ -2,8 +2,10 @@ package service
import (
"context"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"cmautobuy/admin/model"
@@ -164,6 +166,58 @@ func TestPlanInnerCodeRecords_本地没有SybOrder仍可直查远端生成计划
}
}
func TestPlanInnerCodeRecords_持久化现成多明细逐件绑定(t *testing.T) {
db := newTestDB(t)
now := model.NowISO()
user := model.User{UserID: "INNER-CODE-MULTI-USER", Username: "inner-code-multi-user", PasswordHash: "test",
Role: model.RoleAdmin, Status: model.UserActive, PasswordChangedAt: now, CreatedAt: now, UpdatedAt: now}
if err := repository.CreateUser(db, user); err != nil {
t.Fatal(err)
}
record := model.InnerCodeImportRow{
BusinessDate: "2026-08-22", SourceRow: 19, OrderNumber: "ORDER-MULTI-EXISTING",
Stall: "A档#1", SourceSKURaw: "A档#1-黑色-M", SpecRaw: "黑色,M",
SpecKey: NormalizeInnerCodeSpecKey("黑色,M"), InnerCode: "DK-1,DK-2",
SourceDuplicateCount: 2, CreatedByUserID: user.UserID,
}
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
if _, err := repository.UpsertInnerCodeImportRow(tx, record, now); err != nil {
tx.Rollback()
t.Fatal(err)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
var recordID int64
if err := db.QueryRow(`SELECT id FROM syb_inner_code_records WHERE order_number=?`, record.OrderNumber).Scan(&recordID); err != nil {
t.Fatal(err)
}
first := innerCodeTestItem(201, record.SpecRaw, map[string]any{"sku": record.SourceSKURaw, "variationSku": "A档#1", "innerExpCode": ""})
second := innerCodeTestItem(202, record.SpecRaw, map[string]any{"sku": record.SourceSKURaw, "variationSku": "A档#1", "innerExpCode": ""})
first.ProductQty, second.ProductQty = 1, 1
reader := &orderQueryInnerCodeReader{
rowsByOrder: map[string][]syb.StockRow{record.OrderNumber: {{ID: 200, Code: record.OrderNumber}}},
detailsByID: map[int64]syb.StockDetail{200: {ID: 200, Code: record.OrderNumber, Details: []syb.DetailItem{second, first}}},
}
result, err := PlanInnerCodeRecords(context.Background(), db, reader, record.BusinessDate, []int64{recordID})
if err != nil {
t.Fatal(err)
}
var status model.InnerCodeStatus
var detailID int64
var remoteItems string
if err := db.QueryRow(`SELECT status,detail_id,remote_items_json FROM syb_inner_code_records WHERE id=?`, recordID).
Scan(&status, &detailID, &remoteItems); err != nil {
t.Fatal(err)
}
if result.Ready != 1 || status != model.InnerCodeReady || detailID != first.ID || !strings.Contains(remoteItems, `"source":"existing_matched"`) {
t.Fatalf("逐件绑定未持久化 result=%+v status=%s detail=%d items=%s", result, status, detailID, remoteItems)
}
}
func TestPlanInnerCodeRows_精确与标准化规格均受档口约束(t *testing.T) {
records := []model.InnerCodeRecord{
{ID: 1, OrderNumber: "ORDER-1", Stall: "A档#027", SpecRaw: "A-027 黑色,M碼 建議45-55公斤", InnerCode: "DK1", SourceDuplicateCount: 1},
@@ -223,6 +277,62 @@ func TestMatchInnerCodeItemsWithEvidence_重复和证据冲突均阻断(t *testi
}
}
func TestPlanInnerCodeRows_两个单件码复用两条相同现成明细(t *testing.T) {
record := model.InnerCodeRecord{
ID: 41, OrderNumber: "260820SS6E61GG", Stall: "優褲網購#0907",
SourceSKURaw: "優褲網購#0907-灰色【小個子145-155CM】-2XL 【建議62-72kg】",
SpecRaw: "灰色【小個子145-155CM】,2XL 【建議62-72kg】",
InnerCode: "DK260822A130101,DK260822A130301", SourceDuplicateCount: 2,
}
first := innerCodeTestItem(147679521, record.SpecRaw, map[string]any{
"sku": record.SourceSKURaw, "variationSku": "上寮-优裤网购#907#(&17)",
"innerExpCode": "", "purchasePlatform": "", "purchaseCode": "",
})
first.ProductQty = 1
second := innerCodeTestItem(147679527, record.SpecRaw, map[string]any{
"sku": record.SourceSKURaw, "variationSku": "上寮-优裤网购#907#(&17)",
"innerExpCode": "", "purchasePlatform": "", "purchaseCode": "",
})
second.ProductQty = 1
plans := planInnerCodeRows([]model.InnerCodeRecord{record},
map[string][]int64{record.OrderNumber: {76544812}},
map[int64]syb.StockDetail{76544812: {ID: 76544812, Details: []syb.DetailItem{second, first}}})
if len(plans) != 1 || plans[0].Status != model.InnerCodeReady || plans[0].DetailID != first.ID {
t.Fatalf("多明细逐件规划失败: %+v", plans)
}
var items []innerCodeRemoteItem
if err := json.Unmarshal([]byte(plans[0].RemoteItemsJSON), &items); err != nil {
t.Fatalf("逐件规划 JSON 无效: %v raw=%q", err, plans[0].RemoteItemsJSON)
}
want := []innerCodeRemoteItem{
{Code: "DK260822A130101", DetailID: first.ID, Source: innerCodeRemoteSourceExistingMatched, Status: "planned"},
{Code: "DK260822A130301", DetailID: second.ID, Source: innerCodeRemoteSourceExistingMatched, Status: "planned"},
}
if !reflect.DeepEqual(items, want) {
t.Fatalf("逐件绑定不稳定: got=%+v want=%+v", items, want)
}
}
func TestPlanInnerCodeRows_相同候选数与单件码数不一致仍阻断(t *testing.T) {
record := model.InnerCodeRecord{ID: 42, OrderNumber: "ORDER-DUP", Stall: "A档#1",
SourceSKURaw: "A档#1-黑色-M", SpecRaw: "黑色,M",
InnerCode: "DK-1,DK-2", SourceDuplicateCount: 2}
details := make([]syb.DetailItem, 3)
for index := range details {
details[index] = innerCodeTestItem(int64(index+1), record.SpecRaw, map[string]any{
"sku": record.SourceSKURaw, "variationSku": "A档#1", "innerExpCode": "",
})
details[index].ProductQty = 1
}
plans := planInnerCodeRows([]model.InnerCodeRecord{record},
map[string][]int64{record.OrderNumber: {100}},
map[int64]syb.StockDetail{100: {ID: 100, Details: details}})
if len(plans) != 1 || plans[0].Status != model.InnerCodeSkipped || plans[0].RemoteItemsJSON != "" {
t.Fatalf("真实重复候选不应自动规划: %+v", plans)
}
}
func TestInnerCodeStallMatches_中文括号货号与数字前导零(t *testing.T) {
tests := []struct {
name string