feat: 采购详情展示 PDD 核单结果 (#125)

This commit is contained in:
chengma
2026-08-10 19:59:47 +08:00
parent 3554608513
commit a7bc9a940a
6 changed files with 173 additions and 0 deletions
+16
View File
@@ -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)
+64
View File
@@ -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
+64
View File
@@ -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) {
+14
View File
@@ -34,6 +34,20 @@
{{end}}
</dl>
{{if .IsPurchase}}
<h3>PDD 核单结果</h3>
{{if .HasPddOrder}}
<dl class="detail">
<dt>PDD 订单编号</dt><dd>{{.PddOrderNo}}</dd>
<dt>下单时间</dt><dd>{{.PddOrderedAt}}</dd>
<dt>上报时付款状态</dt><dd>{{.PddPaymentStatusText}}</dd>
</dl>
<p class="hint">{{.PddOrderResultText}}</p>
{{else}}
<p class="hint">{{.PddOrderResultText}}</p>
{{end}}
{{end}}
{{if .HasError}}
<h3>错误</h3>
<dl class="detail">
+5
View File
@@ -306,6 +306,11 @@ PDD 商品之所以单独一个模块,是因为它在数据上就是**独立
(PDD 链接,采购任务额外显示目标规格、数量、价格上限);错误信息
(错误码、错误说明,没有错误时不显示这一段)。
采购任务额外显示独立的“PDD 核单结果”:PDD 订单编号、下单时间和
“上报时付款状态”。这些字段从现有 `result_data.purchase` 读取;下单时间转换为
Admin 本地时区,付款状态只是 Client 核单上报时的快照,Admin 不查询 PDD 实时状态。
尚未上报、订单结果不确定和结果格式异常必须使用不同的明确文字;异常时仍保留原始结果。
`[必须]` 采购专有字段(数量、价格上限、目标规格)在采集任务的弹窗里
**整段隐藏**,不显示空行。
+10
View File
@@ -656,6 +656,12 @@ placeholder 写「任务编号 / 订单号 / 商品 ID」,**不要写全「PDD
│ 数量 2 (采购任务才有) │
│ 价格上限 ¥42.00 (采购任务才有) │
├──────────────────────────────────────────────┤
│ PDD 核单结果 (采购任务才有) │
│ PDD 订单编号 PDD-20260810-001 │
│ 下单时间 2026-08-10 19:12 │
│ 上报时付款状态 未付款(Client 上报时) │
│ Admin 不会查询 PDD 实时付款状态。 │
├──────────────────────────────────────────────┤
│ 错误 │
│ 错误码 PDD_PAGE_TIMEOUT │
│ 错误说明 商品页加载超时 │
@@ -666,6 +672,10 @@ placeholder 写「任务编号 / 订单号 / 商品 ID」,**不要写全「PDD
- `[必须]` 采购专有字段(数量、价格上限、目标规格)在采集任务的弹窗里
**整段隐藏**,不显示空行。
- PDD 核单结果只在采购任务显示,订单编号使用可选择复制的普通文本;付款状态标签
必须包含“上报时”,不能暗示为实时状态。
- 尚未上报显示“等待 Client 核单上报”;需人工时显示“订单结果不确定”;结果结构或
带时区时间非法时显示格式异常,并引导展开完整结果诊断。
- 错误段没有错误码/说明时整段不显示。
- `[建议]` `result_data`(Client 提交的完整结果)默认折叠,展开查看;
过长时截断显示。