180 lines
7.9 KiB
Go
180 lines
7.9 KiB
Go
package sybinnercode
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"go-admin/app/goauto/models"
|
|
"go-admin/app/goauto/sybclient"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type fakeMatchReader struct {
|
|
rows map[string][]sybclient.StockRow
|
|
stocks map[int64]sybclient.StockDetail
|
|
listCalls, detailCalls int
|
|
}
|
|
|
|
func (f *fakeMatchReader) ListByOrderNumber(_ context.Context, order string) ([]sybclient.StockRow, error) {
|
|
f.listCalls++
|
|
return f.rows[order], nil
|
|
}
|
|
func (f *fakeMatchReader) DetailListByStock(_ context.Context, ids []int64) ([]sybclient.StockDetail, error) {
|
|
f.detailCalls++
|
|
result := make([]sybclient.StockDetail, 0, len(ids))
|
|
for _, id := range ids {
|
|
if stock, ok := f.stocks[id]; ok {
|
|
result = append(result, stock)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func createMatchJob(t *testing.T, db *gorm.DB, records []models.SYBInnerCodeRecord) string {
|
|
t.Helper()
|
|
for i := range records {
|
|
if err := db.Create(&records[i]).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
id := uuid.NewString()
|
|
ids := make([]uint64, 0, len(records))
|
|
for _, record := range records {
|
|
ids = append(ids, record.ID)
|
|
}
|
|
raw, _ := json.Marshal(ids)
|
|
job := models.SYBInnerCodeMatchJob{ID: id, WorkKey: "test:" + id, BusinessDate: "2026-08-28", RecordIDsJSON: string(raw), Status: "pending", Total: len(records)}
|
|
if err := db.Create(&job).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func TestRunMatchJobOnlyProcessesScopedRecords(t *testing.T) {
|
|
db := testDB(t)
|
|
selected := matchRecord("ORDER-1", "SKU-1", "A#1", "IC-1")
|
|
jobID := createMatchJob(t, db, []models.SYBInnerCodeRecord{selected})
|
|
outside := matchRecord("ORDER-2", "SKU-2", "B#2", "IC-2")
|
|
if err := db.Create(&outside).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reader := &fakeMatchReader{rows: map[string][]sybclient.StockRow{"ORDER-1": {{ID: 10}}}, stocks: map[int64]sybclient.StockDetail{10: {ID: 10, Details: []sybclient.DetailItem{detail(20, "黑色,L", 1, "SKU-1", "A#1", "")}}}}
|
|
if err := RunMatchJob(context.Background(), db, reader, jobID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var untouched models.SYBInnerCodeRecord
|
|
if err := db.First(&untouched, outside.ID).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if untouched.Status != models.SYBInnerCodePending {
|
|
t.Fatalf("out-of-scope record changed to %s", untouched.Status)
|
|
}
|
|
}
|
|
func detail(id int64, spec string, qty int, sku, variation, code string) sybclient.DetailItem {
|
|
return sybclient.DetailItem{ID: id, ProductSpec: spec, ProductQty: qty, Raw: map[string]any{"sku": sku, "variationSku": variation, "innerExpCode": code, "purchasePlatform": "", "purchaseCode": ""}}
|
|
}
|
|
|
|
func matchRecord(order, sku, stall, code string) models.SYBInnerCodeRecord {
|
|
return models.SYBInnerCodeRecord{BusinessDate: "2026-08-28", OrderNumber: order, Stall: stall, SpecKey: "黑色,L", SpecRaw: "黑色,L", SourceSKURaw: sku, Status: models.SYBInnerCodePending, CreatedBy: 1, ImportRequestID: uuid.NewString(), Items: []models.SYBInnerCodeItem{{BusinessDate: "2026-08-28", Code: code, Ordinal: 1, SourceRow: 2}}}
|
|
}
|
|
|
|
func TestRunMatchJobPlansMultiItemWithoutAnyWriteSurface(t *testing.T) {
|
|
db := testDB(t)
|
|
record := models.SYBInnerCodeRecord{BusinessDate: "2026-08-28", OrderNumber: "ORDER-1", Stall: "A#1", SpecKey: "黑色,L", SpecRaw: "黑色,L", SourceSKURaw: "SKU-1", Status: models.SYBInnerCodePending, CreatedBy: 1, ImportRequestID: uuid.NewString(), Items: []models.SYBInnerCodeItem{{BusinessDate: "2026-08-28", Code: "IC-1", Ordinal: 1, SourceRow: 2}, {BusinessDate: "2026-08-28", Code: "IC-2", Ordinal: 2, SourceRow: 3}}}
|
|
jobID := createMatchJob(t, db, []models.SYBInnerCodeRecord{record})
|
|
reader := &fakeMatchReader{rows: map[string][]sybclient.StockRow{"ORDER-1": {{ID: 10, Code: "ORDER-1"}}}, stocks: map[int64]sybclient.StockDetail{10: {ID: 10, Code: "ORDER-1", Details: []sybclient.DetailItem{detail(20, "黑色,L", 2, "SKU-1", "A#1", "")}}}}
|
|
if err := RunMatchJob(context.Background(), db, reader, jobID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var saved models.SYBInnerCodeRecord
|
|
if err := db.Preload("Items").First(&saved).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if saved.Status != models.SYBInnerCodeReady {
|
|
t.Fatalf("status=%s message=%s", saved.Status, saved.ResultMessage)
|
|
}
|
|
var plan models.SYBInnerCodePlan
|
|
if err := db.First(&plan, "record_id = ?", saved.ID).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.DetailID != 20 || plan.PlaceholderCount != 1 || plan.EvidenceHash == "" {
|
|
t.Fatalf("plan=%+v", plan)
|
|
}
|
|
if reader.listCalls != 1 || reader.detailCalls != 1 {
|
|
t.Fatalf("calls list=%d detail=%d", reader.listCalls, reader.detailCalls)
|
|
}
|
|
if err := RunMatchJob(context.Background(), db, reader, jobID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reader.listCalls != 1 {
|
|
t.Fatalf("completed job reran")
|
|
}
|
|
}
|
|
|
|
func TestRunMatchJobRejectsSKUAndStallConflict(t *testing.T) {
|
|
db := testDB(t)
|
|
record := models.SYBInnerCodeRecord{BusinessDate: "2026-08-28", OrderNumber: "ORDER-1", Stall: "B#2", SpecKey: "黑色,L", SpecRaw: "黑色,L", SourceSKURaw: "SKU-A", Status: models.SYBInnerCodePending, CreatedBy: 1, ImportRequestID: uuid.NewString(), Items: []models.SYBInnerCodeItem{{BusinessDate: "2026-08-28", Code: "IC-1", Ordinal: 1, SourceRow: 2}}}
|
|
jobID := createMatchJob(t, db, []models.SYBInnerCodeRecord{record})
|
|
reader := &fakeMatchReader{rows: map[string][]sybclient.StockRow{"ORDER-1": {{ID: 10, Code: "ORDER-1"}}}, stocks: map[int64]sybclient.StockDetail{10: {ID: 10, Details: []sybclient.DetailItem{detail(20, "黑色,L", 1, "SKU-A", "A#1", ""), detail(21, "黑色,L", 1, "SKU-B", "B#2", "")}}}}
|
|
if err := RunMatchJob(context.Background(), db, reader, jobID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var saved models.SYBInnerCodeRecord
|
|
db.First(&saved)
|
|
if saved.Status != models.SYBInnerCodeSkipped || saved.ResultMessage != "原始 SKU 与档口货号冲突,不能自动选择" {
|
|
t.Fatalf("record=%+v", saved)
|
|
}
|
|
}
|
|
|
|
func TestRunMatchJobReservesDifferentDetailsForRecordsInOneBatch(t *testing.T) {
|
|
db := testDB(t)
|
|
base := func(order, sku, stall, code string) models.SYBInnerCodeRecord {
|
|
return models.SYBInnerCodeRecord{BusinessDate: "2026-08-28", OrderNumber: order, Stall: stall, SpecKey: "黑色,L", SpecRaw: "黑色,L", SourceSKURaw: sku, Status: models.SYBInnerCodePending, CreatedBy: 1, ImportRequestID: uuid.NewString(), Items: []models.SYBInnerCodeItem{{BusinessDate: "2026-08-28", Code: code, Ordinal: 1, SourceRow: 2}}}
|
|
}
|
|
jobID := createMatchJob(t, db, []models.SYBInnerCodeRecord{base("ORDER-1", "SKU-A", "A#1", "IC-A"), base("ORDER-1", "SKU-B", "B#2", "IC-B")})
|
|
stock := sybclient.StockDetail{ID: 10, Details: []sybclient.DetailItem{detail(20, "黑色,L", 1, "SKU-A", "A#1", ""), detail(21, "黑色,L", 1, "SKU-B", "B#2", "")}}
|
|
reader := &fakeMatchReader{rows: map[string][]sybclient.StockRow{"ORDER-1": {{ID: 10, Code: "ORDER-1"}}}, stocks: map[int64]sybclient.StockDetail{10: stock}}
|
|
if err := RunMatchJob(context.Background(), db, reader, jobID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var plans []models.SYBInnerCodePlan
|
|
db.Order("record_id").Find(&plans)
|
|
if len(plans) != 2 || plans[0].DetailID == plans[1].DetailID {
|
|
t.Fatalf("plans=%+v", plans)
|
|
}
|
|
}
|
|
|
|
type captureStarter struct {
|
|
jobID string
|
|
committed bool
|
|
}
|
|
|
|
func (c *captureStarter) Start(_ context.Context, db *gorm.DB, jobID string) error {
|
|
c.jobID = jobID
|
|
var count int64
|
|
db.Model(&models.SYBInnerCodeRecord{}).Count(&count)
|
|
c.committed = count > 0
|
|
return nil
|
|
}
|
|
func TestImportCreatesOneMatchJobAndStartsAfterCommit(t *testing.T) {
|
|
db := testDB(t)
|
|
starter := &captureStarter{}
|
|
service := NewService(db).WithMatchEnqueuer(starter)
|
|
rows := [][]any{{"2026-08-28", "ORDER-1", "店铺", "A#1", "SKU-1", "黑色,L", "IC-01", 1}}
|
|
result, err := importRows(t, service, uuid.NewString(), rows)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.MatchJobID == "" || starter.jobID != result.MatchJobID || !starter.committed {
|
|
t.Fatalf("result=%+v starter=%+v", result, starter)
|
|
}
|
|
var count int64
|
|
db.Model(&models.SYBInnerCodeMatchJob{}).Count(&count)
|
|
if count != 1 {
|
|
t.Fatalf("jobs=%d", count)
|
|
}
|
|
}
|