fix(#82): separate PDD linking from mapping

This commit is contained in:
QiuSW
2026-08-25 10:21:10 +08:00
parent 66ea01c02e
commit 7b488e2c68
7 changed files with 92 additions and 22 deletions
+2 -2
View File
@@ -315,13 +315,13 @@ func (s *Service) previewFromDataset(ctx context.Context, id uint64, dataset bat
return item
}
if shopee.PDDProductID == nil {
item.ReasonCode, item.Reason, item.NextAction = "PDD_NOT_LINKED", "尚未关联拼多多商品,请先去匹配", "open_mapping"
item.ReasonCode, item.Reason, item.NextAction = "PDD_NOT_LINKED", "尚未关联 PDD 商品,请先关联", processActionOpenPDDLink
return item
}
item.PDDProductID = shopee.PDDProductID
pdd, found := dataset.pddByID[*shopee.PDDProductID]
if !found {
item.ReasonCode, item.Reason, item.NextAction = "PDD_NOT_FOUND", "关联的拼多多商品不存在,请先重新匹配", "open_mapping"
item.ReasonCode, item.Reason, item.NextAction = "PDD_NOT_FOUND", "关联的 PDD 商品不存在,请重新关联", processActionOpenPDDLink
return item
}
item.PDDGoodsID, item.PDDTitle = pdd.GoodsID, pdd.Title
+17
View File
@@ -43,6 +43,23 @@ func TestBatchPreviewUsesPDDPriceAndExplainsIneligibleRows(t *testing.T) {
}
}
func TestBatchPreviewUsesLinkActionBeforePDDMapping(t *testing.T) {
db := testDB(t)
fixture := seed(t, db, liveCaps(), true)
if err := db.Model(&models.ShopeeProduct{}).Where("id = ?", fixture.shopee.ID).Update("pdd_product_id", nil).Error; err != nil {
t.Fatal(err)
}
response, err := testService(db).BatchPreview(context.Background(), BatchPreviewRequest{SYBProductIDs: []uint64{fixture.syb.ID}})
if err != nil {
t.Fatal(err)
}
item := response.Items[0]
if item.Eligible || item.ReasonCode != "PDD_NOT_LINKED" || item.NextAction != processActionOpenPDDLink || item.ProcessNextAction != processActionOpenPDDLink {
t.Fatalf("PDD link action missing: %+v", item)
}
}
func TestBatchCreateKeepsPartialSuccessAndReplaysCreatedItems(t *testing.T) {
db := testDB(t)
fixture := seed(t, db, liveCaps(), true)
+3 -2
View File
@@ -18,6 +18,7 @@ const (
ProcessStageTaskCreated = "task_created"
ProcessStagePurchaseSucceeded = "purchase_succeeded"
ProcessStageOrderReview = "order_review"
processActionOpenPDDLink = "open_pdd_link"
)
var processStageLabels = map[string]string{
@@ -115,11 +116,11 @@ func processStageFromDataset(id uint64, dataset batchPreviewDataset, preview Bat
return stage(ProcessStageManualAction, "关联的蝦皮商品不存在", "open_shopee")
}
if shopee.PDDProductID == nil {
return stage(ProcessStagePDDUnlinked, "请先关联拼多多商品", "open_mapping")
return stage(ProcessStagePDDUnlinked, "请先关联 PDD 商品", processActionOpenPDDLink)
}
pdd, ok := dataset.pddByID[*shopee.PDDProductID]
if !ok {
return stage(ProcessStagePDDUnlinked, "关联的拼多多商品不存在", "open_mapping")
return stage(ProcessStagePDDUnlinked, "关联的 PDD 商品不存在,请重新关联", processActionOpenPDDLink)
}
if pdd.Status == "disabled" {
return stage(ProcessStageManualAction, "拼多多商品已停用", "open_pdd")
@@ -19,48 +19,49 @@ func TestProcessStageDerivesAllUserFacingStages(t *testing.T) {
}
}
tests := []struct {
name string
want string
set func(*batchPreviewDataset, *BatchPreviewItem)
name string
want string
wantAction string
set func(*batchPreviewDataset, *BatchPreviewItem)
}{
{"待人工处理", ProcessStageManualAction, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"待人工处理", ProcessStageManualAction, "reparse", func(d *batchPreviewDataset, _ *BatchPreviewItem) {
row := d.sybByID[1]
row.ParseStatus = models.SYBParseStatusFailed
d.sybByID[1] = row
}},
{"未关联 PDD", ProcessStagePDDUnlinked, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"未关联 PDD", ProcessStagePDDUnlinked, processActionOpenPDDLink, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
row := d.shopeeByID[2]
row.PDDProductID = nil
d.shopeeByID[2] = row
}},
{"PDD 待采集", ProcessStagePDDPending, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"PDD 待采集", ProcessStagePDDPending, "open_pdd", func(d *batchPreviewDataset, _ *BatchPreviewItem) {
row := d.pddByID[3]
row.Status = "pending"
d.pddByID[3] = row
}},
{"PDD 采集中", ProcessStagePDDCollecting, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"PDD 采集中", ProcessStagePDDCollecting, "open_pdd", func(d *batchPreviewDataset, _ *BatchPreviewItem) {
row := d.pddByID[3]
row.Status = "pending"
d.pddByID[3] = row
d.latestCollectionByPDD[3] = models.CollectionTask{ID: 8, PDDProductID: 3, Status: models.TaskStatusRunning}
}},
{"PDD 采集失败", ProcessStagePDDCollectionFail, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"PDD 采集失败", ProcessStagePDDCollectionFail, "open_pdd", func(d *batchPreviewDataset, _ *BatchPreviewItem) {
row := d.pddByID[3]
row.Status = "pending"
d.pddByID[3] = row
d.latestCollectionByPDD[3] = models.CollectionTask{ID: 8, PDDProductID: 3, Status: models.TaskStatusFailed}
}},
{"颜色待匹配", ProcessStageColorMapping, func(_ *batchPreviewDataset, p *BatchPreviewItem) { p.ReasonCode = CodeMappingRequired }},
{"可创建采购", ProcessStagePurchaseReady, func(_ *batchPreviewDataset, p *BatchPreviewItem) { p.Eligible = true }},
{"已创建任务", ProcessStageTaskCreated, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"颜色待匹配", ProcessStageColorMapping, "open_mapping", func(_ *batchPreviewDataset, p *BatchPreviewItem) { p.ReasonCode = CodeMappingRequired }},
{"可创建采购", ProcessStagePurchaseReady, "", func(_ *batchPreviewDataset, p *BatchPreviewItem) { p.Eligible = true }},
{"已创建任务", ProcessStageTaskCreated, "open_task", func(d *batchPreviewDataset, _ *BatchPreviewItem) {
sybID := uint64(1)
d.latestTaskBySYB[1] = models.PurchaseTask{ID: 11, SYBProductID: &sybID, Status: models.PurchaseTaskStatusRunning}
}},
{"采购成功", ProcessStagePurchaseSucceeded, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"采购成功", ProcessStagePurchaseSucceeded, "open_task", func(d *batchPreviewDataset, _ *BatchPreviewItem) {
sybID := uint64(1)
d.latestTaskBySYB[1] = models.PurchaseTask{ID: 12, SYBProductID: &sybID, Status: models.PurchaseTaskStatusOrderCreated}
}},
{"待人工核对", ProcessStageOrderReview, func(d *batchPreviewDataset, _ *BatchPreviewItem) {
{"待人工核对", ProcessStageOrderReview, "open_task", func(d *batchPreviewDataset, _ *BatchPreviewItem) {
sybID := uint64(1)
d.latestTaskBySYB[1] = models.PurchaseTask{ID: 13, SYBProductID: &sybID, Status: models.PurchaseTaskStatusOrderResultUnknown}
}},
@@ -71,8 +72,8 @@ func TestProcessStageDerivesAllUserFacingStages(t *testing.T) {
preview := BatchPreviewItem{SYBProductID: 1}
tt.set(&dataset, &preview)
got := processStageFromDataset(1, dataset, preview)
if got.Stage != tt.want || got.Label != tt.name {
t.Fatalf("got stage=%q label=%q, want %q/%q", got.Stage, got.Label, tt.want, tt.name)
if got.Stage != tt.want || got.Label != tt.name || got.NextAction != tt.wantAction {
t.Fatalf("got stage=%q label=%q action=%q, want %q/%q/%q", got.Stage, got.Label, got.NextAction, tt.want, tt.name, tt.wantAction)
}
})
}
@@ -83,3 +84,20 @@ func TestValidProcessStageRejectsInternalOrUnknownValues(t *testing.T) {
t.Fatal("process stage validation accepted an internal or unknown value")
}
}
func TestProcessStageMissingPDDUsesRelinkAction(t *testing.T) {
pddID := uint64(3)
shopeeID := uint64(2)
dataset := batchPreviewDataset{
sybByID: map[uint64]models.SYBProduct{1: {ID: 1, ParseStatus: models.SYBParseStatusSuccess, ShopeeProductID: &shopeeID}},
shopeeByID: map[uint64]models.ShopeeProduct{2: {ID: 2, PDDProductID: &pddID}},
pddByID: map[uint64]models.PDDProduct{},
latestTaskBySYB: make(map[uint64]models.PurchaseTask),
latestCollectionByPDD: make(map[uint64]models.CollectionTask),
}
got := processStageFromDataset(1, dataset, BatchPreviewItem{SYBProductID: 1})
if got.Stage != ProcessStagePDDUnlinked || got.NextAction != processActionOpenPDDLink || got.Reason != "关联的 PDD 商品不存在,请重新关联" {
t.Fatalf("missing PDD did not request relink: %+v", got)
}
}