diff --git a/admin/main_test.go b/admin/main_test.go index f3826fb..b16e5d8 100644 --- a/admin/main_test.go +++ b/admin/main_test.go @@ -84,6 +84,22 @@ func TestPddDetailRecollect_使用独立入口并可选客户端(t *testing.T) { } } +func TestTaskDetail_采购核单结果使用独立只读分组(t *testing.T) { + content, err := os.ReadFile("templates/task/detail_modal.html") + if err != nil { + t.Fatal(err) + } + page := string(content) + for _, want := range []string{ + "PDD 核单结果", "PDD 订单编号", "下单时间", "上报时付款状态", + "{{if .IsPurchase}}", "{{if .HasPddOrder}}", "完整结果(result_data)", + } { + if !strings.Contains(page, want) { + t.Errorf("采购任务详情缺少 %q", want) + } + } +} + func TestTemplatesAndRoutesCanBeBuiltWithoutDatabaseConnection(t *testing.T) { if _, err := newRouter(nil); err != nil { t.Fatalf("模板或路由组装失败: %v", err) diff --git a/admin/service/task.go b/admin/service/task.go index 9890fa2..3f97bbb 100644 --- a/admin/service/task.go +++ b/admin/service/task.go @@ -345,6 +345,14 @@ type TaskDetailView struct { QuantityText string PriceLimitText string + // PDD 核单结果只在采购任务详情显示。付款状态是 Client 核单上报时的快照, + // 不是 Admin 对 PDD 的实时查询结果。 + HasPddOrder bool + PddOrderNo string + PddOrderedAt string + PddPaymentStatusText string + PddOrderResultText string + HasError bool ErrorCode string ErrorMessage string @@ -377,6 +385,7 @@ func GetTaskDetail(db *sql.DB, taskID string) (*TaskDetailView, error) { v.SpecText = specText(t.PddOptions) v.QuantityText = quantityText(t.Quantity) v.PriceLimitText = priceLimitText(t.MaxPriceCent) + setPddOrderResult(v, *t) } if t.ErrorCode != "" || t.ErrorMessage != "" { @@ -392,6 +401,61 @@ func GetTaskDetail(db *sql.DB, taskID string) (*TaskDetailView, error) { return v, nil } +// purchaseResult 只声明详情页需要展示的字段,避免把 Client 的完整结果结构 +// 复制到 Admin。其余原始字段仍保存在 result_data 并可在详情底部展开查看。 +type purchaseResult struct { + Purchase *struct { + OrderNo string `json:"order_no"` + OrderedAt string `json:"ordered_at"` + PaymentStatus string `json:"payment_status"` + MatchStatus string `json:"match_status"` + OrderSubmitted bool `json:"order_submitted"` + } `json:"purchase"` +} + +// setPddOrderResult 把采购结果翻成模板可直接显示的文字。 +// 解析失败只影响结构化摘要,不能阻止详情页打开,也不能隐藏原始 result_data。 +func setPddOrderResult(v *TaskDetailView, task model.Task) { + raw := strings.TrimSpace(task.ResultData) + if raw == "" { + switch task.Status { + case model.TaskManualReview: + v.PddOrderResultText = "订单结果不确定,请结合错误信息人工核对。" + case model.TaskFailed, model.TaskCancelled: + v.PddOrderResultText = "任务未产生可展示的 PDD 核单结果。" + default: + v.PddOrderResultText = "等待 Client 核单上报。" + } + return + } + + var result purchaseResult + if err := json.Unmarshal([]byte(raw), &result); err != nil || result.Purchase == nil { + v.PddOrderResultText = "采购结果格式异常,请展开完整结果进行诊断。" + return + } + p := result.Purchase + if p.MatchStatus != "matched" || !p.OrderSubmitted { + v.PddOrderResultText = "本次结果没有已匹配的 PDD 订单。" + return + } + if strings.TrimSpace(p.OrderNo) == "" || p.PaymentStatus != "unpaid" { + v.PddOrderResultText = "采购结果格式异常,请展开完整结果进行诊断。" + return + } + orderedAt, ok := model.ParseISO(strings.TrimSpace(p.OrderedAt)) + if !ok { + v.PddOrderResultText = "采购结果中的下单时间格式异常,请展开完整结果进行诊断。" + return + } + + v.HasPddOrder = true + v.PddOrderNo = strings.TrimSpace(p.OrderNo) + v.PddOrderedAt = orderedAt.Local().Format("2006-01-02 15:04") + v.PddPaymentStatusText = "未付款(Client 上报时)" + v.PddOrderResultText = "Admin 不会查询 PDD 实时付款状态。" +} + func orPlaceholder(s string) string { if s == "" { return placeholder diff --git a/admin/service/task_test.go b/admin/service/task_test.go index 21af163..a16e2f1 100644 --- a/admin/service/task_test.go +++ b/admin/service/task_test.go @@ -3,6 +3,7 @@ package service import ( "database/sql" "fmt" + "strings" "testing" "cmautobuy/admin/model" @@ -368,6 +369,66 @@ func TestGetTaskDetail_采购任务显示专有字段(t *testing.T) { } } +func TestGetTaskDetail_采购成功独立显示PDD核单结果(t *testing.T) { + db := newTestDB(t) + insertTestTask(t, db, testTaskParams{ + taskID: "PUR-ORDER", taskType: model.TaskPurchase, status: model.TaskSucceeded, + resultData: `{"purchase":{"order_no":"PDD-20260810-001","ordered_at":"2026-08-10T11:12:13Z","payment_status":"unpaid","match_status":"matched","order_submitted":true}}`, + }) + + d, err := GetTaskDetail(db, "PUR-ORDER") + if err != nil { + t.Fatal(err) + } + if !d.HasPddOrder || d.PddOrderNo != "PDD-20260810-001" { + t.Fatalf("PDD 订单编号没有结构化显示: %+v", d) + } + if d.PddOrderedAt != formatLocalTime("2026-08-10T11:12:13Z") { + t.Errorf("UTC 下单时间应转为本地时间,实际 %q", d.PddOrderedAt) + } + if d.PddPaymentStatusText != "未付款(Client 上报时)" || + d.PddOrderResultText != "Admin 不会查询 PDD 实时付款状态。" { + t.Errorf("付款状态说明不明确: status=%q help=%q", + d.PddPaymentStatusText, d.PddOrderResultText) + } + if !d.HasResult || !strings.Contains(d.ResultData, `"order_no":"PDD-20260810-001"`) { + t.Error("结构化展示后仍必须保留原始结果") + } +} + +func TestGetTaskDetail_核单结果状态分别显示(t *testing.T) { + db := newTestDB(t) + tests := []struct { + name string + status model.TaskStatus + resultData string + want string + }{ + {"等待上报", model.TaskClaimed, "", "等待 Client 核单上报。"}, + {"人工处理", model.TaskManualReview, "", "订单结果不确定,请结合错误信息人工核对。"}, + {"非法JSON", model.TaskSucceeded, `{`, "采购结果格式异常,请展开完整结果进行诊断。"}, + {"缺少purchase", model.TaskSucceeded, `{}`, "采购结果格式异常,请展开完整结果进行诊断。"}, + {"尚未匹配", model.TaskSucceeded, `{"purchase":{"match_status":"not_found"}}`, "本次结果没有已匹配的 PDD 订单。"}, + {"时间非法", model.TaskSucceeded, `{"purchase":{"order_no":"PDD-1","ordered_at":"2026-08-10 11:12","payment_status":"unpaid","match_status":"matched","order_submitted":true}}`, "采购结果中的下单时间格式异常,请展开完整结果进行诊断。"}, + } + for i, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + taskID := fmt.Sprintf("PUR-STATE-%d", i) + insertTestTask(t, db, testTaskParams{ + taskID: taskID, taskType: model.TaskPurchase, + status: tc.status, resultData: tc.resultData, + }) + d, err := GetTaskDetail(db, taskID) + if err != nil { + t.Fatal(err) + } + if d.HasPddOrder || d.PddOrderResultText != tc.want { + t.Errorf("状态说明不对: HasPddOrder=%t text=%q", d.HasPddOrder, d.PddOrderResultText) + } + }) + } +} + func TestGetTaskDetail_采集任务不带采购专有字段(t *testing.T) { db := newTestDB(t) insertTestTask(t, db, testTaskParams{taskID: "COL-1", taskType: model.TaskCollect, status: model.TaskPending, pddGoodsID: "111"}) @@ -382,6 +443,9 @@ func TestGetTaskDetail_采集任务不带采购专有字段(t *testing.T) { if d.SpecText != "" || d.QuantityText != "" || d.PriceLimitText != "" { t.Errorf("采集任务不该填充采购专有字段: spec=%q qty=%q price=%q", d.SpecText, d.QuantityText, d.PriceLimitText) } + if d.HasPddOrder || d.PddOrderResultText != "" { + t.Error("采集任务不应生成 PDD 核单结果展示") + } } func TestGetTaskDetail_有错误信息时显示(t *testing.T) { diff --git a/admin/templates/task/detail_modal.html b/admin/templates/task/detail_modal.html index b756aaa..06196bd 100644 --- a/admin/templates/task/detail_modal.html +++ b/admin/templates/task/detail_modal.html @@ -34,6 +34,20 @@ {{end}} + {{if .IsPurchase}} +
{{.PddOrderResultText}}
+ {{else}} +{{.PddOrderResultText}}
+ {{end}} + {{end}} + {{if .HasError}}