✓
diff --git a/backend-api/internal/transport/webui/types.go b/backend-api/internal/transport/webui/types.go
index a0cadfe..c57773a 100644
--- a/backend-api/internal/transport/webui/types.go
+++ b/backend-api/internal/transport/webui/types.go
@@ -65,6 +65,7 @@ type Task struct {
ExecutionReport *ExecutionReport
Candidates []AuthorizationCandidate
OrderAuthorizations []OrderAuthorization
+ OrderSubmissions []OrderSubmission
}
type AuthorizationCandidate struct {
@@ -88,6 +89,25 @@ type OrderAuthorization struct {
CreatedAt time.Time
}
+type OrderSubmission struct {
+ ID string
+ Status string
+ StatusLabel string
+ ExpectedTitle string
+ ExpectedSKU string
+ ExpectedQuantity int
+ ExpectedUnitPrice string
+ ExpectedTotalPrice string
+ PlatformOrderNo string
+ PlatformOrderedAt time.Time
+ PlatformOrderStatus string
+ EvidenceContentURL string
+ ManualReasonLabel string
+ FencedAt time.Time
+ ReconciledAt time.Time
+ ManualReviewAt time.Time
+}
+
type ExecutionReport struct {
Events []ExecutionReportEvent
Evidence []ExecutionReportEvidence
diff --git a/backend-api/internal/transport/webui/usecase_adapter.go b/backend-api/internal/transport/webui/usecase_adapter.go
index 89dcb32..e723c50 100644
--- a/backend-api/internal/transport/webui/usecase_adapter.go
+++ b/backend-api/internal/transport/webui/usecase_adapter.go
@@ -280,9 +280,91 @@ func taskFromDetail(detail domain.TaskDetail) Task {
orderAuthorizationFrom(authorization),
)
}
+ for _, submission := range detail.OrderSubmissions {
+ task.OrderSubmissions = append(
+ task.OrderSubmissions,
+ orderSubmissionFrom(submission),
+ )
+ }
return task
}
+func orderSubmissionFrom(
+ submission domain.OrderSubmission,
+) OrderSubmission {
+ unitPrice := submission.ExpectedUnitPriceCents
+ totalPrice := submission.ExpectedTotalPriceCents
+ result := OrderSubmission{
+ ID: submission.ID,
+ Status: string(submission.Status),
+ StatusLabel: orderSubmissionStatusLabel(submission.Status),
+ ExpectedTitle: submission.ExpectedTitle,
+ ExpectedSKU: submission.ExpectedSKU,
+ ExpectedQuantity: submission.ExpectedQuantity,
+ ExpectedUnitPrice: *domain.FormatOptionalCNY(&unitPrice),
+ ExpectedTotalPrice: *domain.FormatOptionalCNY(&totalPrice),
+ FencedAt: submission.FencedAt,
+ }
+ if submission.PlatformOrderNo != nil {
+ result.PlatformOrderNo = *submission.PlatformOrderNo
+ }
+ if submission.PlatformOrderedAt != nil {
+ result.PlatformOrderedAt = *submission.PlatformOrderedAt
+ }
+ if submission.PlatformOrderStatus != nil {
+ result.PlatformOrderStatus = *submission.PlatformOrderStatus
+ }
+ if submission.ReconciliationEvidenceAssetID != nil {
+ result.EvidenceContentURL = "/api/v1/tasks/" +
+ submission.TaskID + "/evidence/" +
+ *submission.ReconciliationEvidenceAssetID + "/content"
+ }
+ if submission.ManualReasonCode != nil {
+ result.ManualReasonLabel = orderSubmissionManualReasonLabel(
+ *submission.ManualReasonCode,
+ )
+ }
+ if submission.ReconciledAt != nil {
+ result.ReconciledAt = *submission.ReconciledAt
+ }
+ if submission.ManualReviewAt != nil {
+ result.ManualReviewAt = *submission.ManualReviewAt
+ }
+ return result
+}
+
+func orderSubmissionStatusLabel(status domain.OrderSubmissionStatus) string {
+ switch status {
+ case domain.OrderSubmissionFenced:
+ return "正在对账"
+ case domain.OrderSubmissionManualReview:
+ return "需要人工对账"
+ case domain.OrderSubmissionReconciled:
+ return "待人工确认付款"
+ default:
+ return "未知状态"
+ }
+}
+
+func orderSubmissionManualReasonLabel(code string) string {
+ switch code {
+ case "ORDER_NOT_FOUND":
+ return "未找到符合条件的新订单"
+ case "ORDER_AMBIGUOUS":
+ return "找到多个可能订单"
+ case "ORDER_FIELDS_INCOMPLETE":
+ return "订单字段不完整"
+ case "ORDER_PAGE_UNKNOWN":
+ return "订单页面无法确认"
+ case "RISK_OR_PAYMENT_BOUNDARY":
+ return "遇到风控或付款边界"
+ case "EVIDENCE_UNAVAILABLE":
+ return "无法取得对账证据"
+ default:
+ return "订单需要人工核对"
+ }
+}
+
func orderAuthorizationFrom(
authorization domain.OrderAuthorization,
) OrderAuthorization {
diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md
index 7b6daaa..90fdf82 100644
--- a/docs/00-ai-start-here.md
+++ b/docs/00-ai-start-here.md
@@ -61,8 +61,8 @@ T-205 原子领取/租约状态机、T-206 Android 登录/有限离线、T-207
规格组合/价格核验均已完成。T-208 的原始候选观测、模型评估、确定性推荐、逐候选
结构化人工理由和修订历史也已完成。T-214 商品持久身份和重新定位指纹、T-215
Admin 候选确认、不可变待投递授权、T-216 设备命令可靠投递及 T-217 已授权商品
-重新定位与订单 dry-run、T-218 单次订单提交围栏和订单回读均已完成;T-219 Admin
-待付款提醒与端到端验收正在开发。
+重新定位与订单 dry-run、T-218 单次订单提交围栏和订单回读,以及 T-219 Admin
+待付款提醒与端到端验收均已完成。下一任务是 T-301 P0 UI 完整交互验收。
不得直接把候选链接或列表 ordinal 当成授权。
手机从管理后端领取任务并回传结果,VLM、拼多多自动化和人工确认在 App 本地完成。
T-206 增加有限离线执行;T-207 已复用 Roubao 端上 OpenAI 兼容适配器并加密本地 Key。
diff --git a/docs/08-interaction-checklist.md b/docs/08-interaction-checklist.md
index c64f61c..1953a7b 100644
--- a/docs/08-interaction-checklist.md
+++ b/docs/08-interaction-checklist.md
@@ -17,7 +17,7 @@
| IX-008 | US-006 | App/管理端错误状态 | 自动失败、取消、重试上传 | 显示结构化原因和恢复动作 | P0 | 已定 |
| IX-009 | US-008 | App 候选理由/管理端决策详情 | 接受、拒绝、改选或修正 | 保存逐候选结构化人工标签 | P1 | T-208 已实现 |
| IX-010 | US-009 | App 独立执行设置/同步状态 | 配置模式、离线执行或补报 | 授权内独立执行并可审计同步 | P0 | T-206 离线控制已实现,结果补报待 T-207 |
-| IX-011 | US-010 | Admin 候选授权/App 订单执行 | 选择候选并授权、设备领取执行 | 创建一笔可对账的待付款订单并提醒人工付款 | P0 | T-215 至 T-218 已完成;T-219 开发中 |
+| IX-011 | US-010 | Admin 候选授权/App 订单执行 | 选择候选并授权、设备领取执行 | 创建一笔可对账的待付款订单并提醒人工付款 | P0 | T-215 至 T-219 已完成 |
## IX-001 管理 Web 登录
diff --git a/docs/current-state.md b/docs/current-state.md
index a27a04c..daf0a86 100644
--- a/docs/current-state.md
+++ b/docs/current-state.md
@@ -5,8 +5,8 @@
## 当前快照
- 日期:2026-07-28
-- 阶段:T-219 Admin 待付款提醒与端到端验收已领取,合约冻结中
-- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-218
+- 阶段:T-219 Admin 待付款提醒与端到端验收已完成
+- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-219
均按文档提交、实现提交的顺序纳入历史
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
- Android:固定 `main@c8a6d7f03422eb01744b01f3ee77bf7757741f7e`;MIT 许可证已保留
@@ -17,10 +17,10 @@
- 本机 Android 工具:JDK 17.0.13、Command-line Tools 22.0、SDK 34、
Build Tools 34.0.0、Platform Tools/ADB 37.0.0;用户级 SDK 环境变量已设置
- Android Studio:未安装;`winget` 静默安装卡住后已终止,不阻塞命令行构建
-- 测试:T-218 Android Debug/Release 单元测试与构建和根 `init.ps1` 通过;
- Debug APK `1.4.15 (20)` 已覆盖安装到 PKG110
-- 后端测试:T-218 运行 `go test ./...`、`go test -race ./...`、`go vet ./...`;
- v11 migration 降级保护及 fence/replay/manual-review/reconcile HTTP 集成测试通过
+- 测试:T-219 Android Debug/Release 单元测试与构建和根 `init.ps1` 通过;
+ Debug APK `1.4.16 (21)` 已覆盖安装到 PKG110
+- 后端测试:T-219 运行 `go test ./...`、`go test -race ./...`、`go vet ./...`;
+ 唯一 submission 的设备到 Admin HTTP 闭环及三态 SSR 测试通过
- 原型:4 个管理 Web 页面和 7 个 Android 页面均可离线独立打开;Playwright
以 1440×900、390×844、360×800 验证 36 个页面/视口组合,无页面横向溢出、
脚本错误或外部请求,Android 可见交互控件均不小于 44px
@@ -80,6 +80,10 @@
`CLICK_ASSUMED`,最多点击一次精确“提交订单”;进程/网络不确定和租约到期后只
回读待付款订单或转人工,绝不重复提交或点击支付。v11 保存围栏、幂等操作、订单号/
时间、证据和审计事件,唯一对账后才写 `order_submitted=true`。
+- T-219 待付款提醒:Admin API/SSR 按任务鉴权读取 submission,区分围栏中、人工
+ 对账和唯一对账成功;成功态展示完整订单号、平台时间、授权 SKU/数量/金额及受控
+ 证据,只提醒采购员打开拼多多人工核对付款。Roubao 显示相同终态;Web/App 均没有
+ 付款、重复提交或自动支付动作。隔离数据库真实 SSR 已通过三个响应式视口验收。
- Workflow:纯 Kotlin runner 已支持步骤 timeout、最多 3 次 retry、安全阻塞、
用户停止和单 runner 并发拒绝;T-102 已接入搜索加有界候选采集五步
- TaskSource:严格 CLI 已生成并验证真实私有 ProbeTask;默认 APK 不含私有 fixture
@@ -93,7 +97,7 @@
- 离线执行:默认 30 分钟有限授权和 30 秒 best-effort heartbeat 已实现;PKG110
真机断开临时后端 95 秒后保持同一 execution,重连后滑动续期;到期持久安全停止,
RUNNING 不自动重新分配
-- 测试设备:OnePlus PKG110,Android 16/API 36;已安装肉包 `1.4.15 (20)`;拼多多
+- 测试设备:OnePlus PKG110,Android 16/API 36;已安装肉包 `1.4.16 (21)`;拼多多
`8.17.0 (81700)`
- 设备就绪:拼多多首页、文字/图片搜索、双列候选卡、详情截图和返回均已通过 8.17.0
真机验证;ColorOS 本次覆盖安装后移除了肉包采购无障碍,重新启用后服务已连接,
@@ -108,7 +112,7 @@
已用 CLI 真实导入并逐字段/图片哈希验证,生成物位于被忽略的 `.local/`
- 标准启动路径:`$env:RUN_START_COMMAND="1"; .\init.ps1`
- 标准验证路径:`.\init.ps1`
-- 当前 blocker:T-219 实现无代码阻塞;当前真机确认页缺少已配置收货地址且显示
+- 当前 blocker:当前真机确认页缺少已配置收货地址且显示
“立即支付/先用后付”,T-218 已按合约证明不创建围栏、不点击。不能为测试自动添加
地址或跨过付款边界。真实 VLM 服务地址、模型、
设备级测试凭证、成本上限和数据留存尚未确认;当前只支持单 SKU/JPEG;候选探针
@@ -146,6 +150,7 @@
| `docs/tasks/T-216.md` | DONE | App 主动拉取并先加密落盘再确认同一条下单命令 |
| `docs/tasks/T-217.md` | DONE | 重新核对已授权商品并选择 SKU/数量,停在最终提交前 |
| `docs/tasks/T-218.md` | DONE | 单次提交围栏、最多一次提交动作和待付款订单唯一对账 |
+| `docs/tasks/T-219.md` | DONE | Admin 待付款提醒、Roubao 终态和跨端闭环验收 |
| `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 |
| `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 |
| `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 |
@@ -156,9 +161,9 @@
## 任务摘要
-- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-218。
-- 进行中:T-219 Admin 待付款提醒与端到端验收。
-- 下一步:完成 T-219 后进入 T-301 P0 UI 完整交互验收。
+- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-219。
+- 进行中:无。
+- 下一步:进入 T-301 P0 UI 完整交互验收。
## 当前可运行内容
diff --git a/docs/tasks/T-219.md b/docs/tasks/T-219.md
index c627c91..161fd5b 100644
--- a/docs/tasks/T-219.md
+++ b/docs/tasks/T-219.md
@@ -4,7 +4,7 @@ title: Admin 待付款提醒与端到端验收
phase: 2
deps:
- T-218
-status: DOING
+status: DONE
created: 2026-07-28
context_ref: a77d8be
work_branch: null
@@ -106,13 +106,13 @@ T-218 已把唯一对账成功的待付款订单保存到 `order_submissions`,
## 验收要点
-- [ ] repository 按任务和 Admin subject 读取稳定排序的 submission,空值不报错。
-- [ ] Admin API 仅鉴权返回对账字段且 `no-store`,订单号不进 URL/日志。
-- [ ] SSR 正确区分 FENCED、MANUAL_REVIEW、RECONCILED 和历史未下单 SUCCEEDED。
-- [ ] RECONCILED 展示订单号、时间、SKU、数量、金额、截图及人工付款提醒。
-- [ ] Web/App 均没有付款按钮、重试提交或自动支付动作。
-- [ ] Go test/race/vet、Android test/Debug/Release、根验证与响应式 Playwright 通过。
-- [ ] 最小跨端 fixture 证明唯一 submission 和 Admin 提醒闭环。
+- [x] repository 按任务和 Admin subject 读取稳定排序的 submission,空值不报错。
+- [x] Admin API 仅鉴权返回对账字段且 `no-store`,订单号不进 URL/日志。
+- [x] SSR 正确区分 FENCED、MANUAL_REVIEW、RECONCILED 和历史未下单 SUCCEEDED。
+- [x] RECONCILED 展示订单号、时间、SKU、数量、金额、截图及人工付款提醒。
+- [x] Web/App 均没有付款按钮、重试提交或自动支付动作。
+- [x] Go test/race/vet、Android test/Debug/Release、根验证与响应式 Playwright 通过。
+- [x] 最小跨端 fixture 证明唯一 submission 和 Admin 提醒闭环。
## 边界
@@ -125,3 +125,14 @@ T-218 已把唯一对账成功的待付款订单保存到 `order_submissions`,
- 2026-07-28:T-218 实现提交 `a77d8be` 后领取。冻结 Admin 三态展示、鉴权订单字段、
历史成功文案兼容、Roubao 终态提醒和隔离数据库端到端验收。
+- 2026-07-28:文档合约提交为 `c2ec98d`。`TaskDetail`、Admin API 和 SSR 已读取
+ submission;管理详情区分 `FENCED`、`MANUAL_REVIEW`、`RECONCILED`,唯一对账后
+ 展示完整订单号、平台时间、授权快照、受控证据和人工付款提醒。Roubao 同步展示
+ 对账终态,未增加付款或重复提交动作。
+- 2026-07-28:设备到 Admin 的自动化 fixture 已覆盖创建、领取、候选、授权、命令
+ 确认、dry-run、围栏、唯一对账和提醒,断言每个授权只有一个 submission。真实 SSR
+ 使用隔离数据库在 1440x900、390x844、360x800 验证,无脚本错误、横向溢出或付款
+ 操作,证据请求成功;生产数据库未修改,未创建真实拼多多订单。
+- 2026-07-28:`go test ./...`、`go test -race ./...`、`go vet ./...`、Android
+ Debug/Release 单元测试与构建、根 `init.ps1` 全部通过。Debug APK
+ `1.4.16 (21)` 已覆盖安装到 PKG110,并恢复、确认肉包无障碍服务。