docs: import wiki at afc651f75a3a
+119
@@ -0,0 +1,119 @@
|
||||
<!-- docs-wiki-sync:docs/tasks/T-249.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
|
||||
> 同步来源:[`docs/tasks/T-249.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-249.md) · commit `afc651f75a3a`
|
||||
|
||||
---
|
||||
id: T-249
|
||||
title: 采购任务安全重试与重复采购防护
|
||||
phase: 2
|
||||
deps:
|
||||
- T-218
|
||||
- T-223
|
||||
- T-248
|
||||
status: DONE
|
||||
created: 2026-07-30
|
||||
context_ref: 773a3cd
|
||||
work_branch: null
|
||||
write_paths:
|
||||
- docs/tasks/T-249.md
|
||||
- docs/current-state.md
|
||||
- docs/api.md
|
||||
- backend-api/migrations/**
|
||||
- backend-api/internal/domain/**
|
||||
- backend-api/internal/usecase/**
|
||||
- backend-api/internal/repository/sqlite/**
|
||||
- backend-api/internal/transport/httpapi/**
|
||||
- backend-api/internal/transport/webui/**
|
||||
---
|
||||
|
||||
## 需求 / 背景
|
||||
|
||||
ERP 货运商品当前按 `(freight_order_item_id, source_revision)` 只允许创建一条采购需求,
|
||||
同一采购需求也只关联一个采购任务。前一个唯一约束可以防止页面双击、网络重试和多人操作
|
||||
形成重复采购,必须保留;但采购任务因设备中断、自动化失败或下单前人工取消进入
|
||||
`FAILED/CANCELED` 后没有恢复入口,采购人员只能看到历史任务,无法继续采购。
|
||||
|
||||
本任务不开放“再次创建相同采购订单”。同一 ERP 商品 revision 继续只有一个业务采购需求
|
||||
和一个采购任务身份;符合安全条件时,将原任务重新排入 `PENDING`,由 Roubao 领取后创建
|
||||
新的 execution attempt。既有候选、证据、人工选择、失败结果和事件全部保留。
|
||||
|
||||
## 修订契约
|
||||
|
||||
### 需求和任务身份
|
||||
|
||||
- `UNIQUE (freight_order_item_id, source_revision)` 保持不变;重复创建采购需求继续幂等返回
|
||||
原需求。
|
||||
- `procurement_requests.purchase_task_id` 与 `purchase_task_sources` 的一对一来源关系保持
|
||||
不变;安全重试不复制参考图、不创建第二个任务 ID,也不改写 ERP 来源快照。
|
||||
- Admin 文案使用“重新执行采购任务”,不得表示已创建新的拼多多订单。
|
||||
|
||||
### 可重试状态和下单围栏
|
||||
|
||||
- 只允许 `FAILED` 或 `CANCELED` 任务重新排队;`PENDING/CLAIMED/RUNNING/
|
||||
WAITING_CONFIRMATION/SUCCEEDED` 一律拒绝。
|
||||
- 事务内检查该任务不存在任何 `order_submissions` 记录,且历史 execution/outcome 均未
|
||||
标记 `order_submitted=true`。只要已创建 `FENCED/RECONCILED/MANUAL_REVIEW` 提交记录,
|
||||
或是否下单无法确认,就保持禁止重试。
|
||||
- 重试成功将任务改为 `PENDING`、增加 `version`、清除当前 claim 和取消控制字段;任务
|
||||
标题、SKU、数量、预算、参考图、来源关联和全部历史执行数据保持不变。
|
||||
- Roubao 再次领取时沿用既有 claim generation 和 execution attempt 递增机制;旧 claim、
|
||||
authorization、command 和 execution 不能授权新一轮动作。
|
||||
|
||||
### 原因、审计和幂等
|
||||
|
||||
- 重试必须提交原因码:`AUTOMATION_FAILED`、`DEVICE_INTERRUPTED`、
|
||||
`ADMIN_CANCELED` 或 `OTHER`。`OTHER` 必须填写说明,说明去除首尾空格后最多 500 个
|
||||
UTF-8 字节。
|
||||
- v20 保存不可变重试审计:任务、重试序号、重试前状态、原因、操作人和时间;同时产生
|
||||
`TASK_REQUEUED` 任务事件。
|
||||
- Admin API 和 SSR 都要求幂等键。相同键和相同请求返回当前任务;相同键不同请求返回
|
||||
幂等冲突。并发请求最多一次从终态切换到 `PENDING`。
|
||||
- 稳定错误区分状态冲突和下单不确定:前者为 `TASK_RETRY_NOT_ALLOWED`,后者为
|
||||
`TASK_RETRY_ORDER_UNCERTAIN`,不得用通用 500 掩盖。
|
||||
|
||||
### Admin 交互
|
||||
|
||||
- 任务详情仅在状态和下单历史表明可重试时显示“重新执行”入口;表单要求选择原因,
|
||||
`OTHER` 提供说明并在服务端再次校验。
|
||||
- 有提交围栏或待付款订单时不显示可执行按钮,明确提示必须先核对拼多多订单,禁止重复
|
||||
下单。
|
||||
- 重试成功返回同一任务详情并显示“任务已重新排队”;货运详情继续链接同一采购任务。
|
||||
|
||||
## 方案
|
||||
|
||||
1. v20 增加 `task_retries` 审计表,并重建 `task_events` 约束以允许
|
||||
`TASK_REQUEUED`;Down 在已有重试记录或事件时 fail-closed。
|
||||
2. `TaskService` 增加受校验的 retry command;领域层集中维护原因码、状态判断和长度限制。
|
||||
3. SQLite immediate 写事务完成幂等读取、安全门禁、审计插入、任务状态更新和事件写入,
|
||||
并同时检查 `task_executions`、`execution_outcomes` 与 `order_submissions`。
|
||||
4. Admin JSON API 增加 `POST /api/v1/tasks/:id/retry`;SSR 增加
|
||||
`POST /tasks/:id/retry` 和任务详情重试表单。
|
||||
5. 任务详情返回是否可重试及阻塞原因;最终安全裁决始终在仓储事务内完成,页面判断只
|
||||
用于交互提示。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- [x] 同一货运商品 revision 仍不能创建第二条采购需求或第二个任务 ID。
|
||||
- [x] `FAILED/CANCELED` 且无下单痕迹的任务可原子恢复为 `PENDING`。
|
||||
- [x] 新一次 Roubao 领取创建递增的 execution attempt,旧证据和结果仍可审计。
|
||||
- [x] 活跃、成功、已围栏、待人工复核或待付款任务均不能重试。
|
||||
- [x] 重试原因、操作人、原状态和时间可查询;`OTHER` 无说明时拒绝。
|
||||
- [x] API/SSR 幂等、并发和稳定错误码测试通过。
|
||||
- [x] v20 上下迁移、Go 单测、race、vet 和入口构建通过。
|
||||
|
||||
## 边界
|
||||
|
||||
- 本任务不实现拆单、部分数量补采或同一需求多任务;这些场景必须先扩展拼多多订单的
|
||||
已付款、取消、关闭、退款和实购数量状态,不能仅凭任务失败状态猜测。
|
||||
- 不删除或覆盖旧 execution、候选、授权、命令、证据、结果、提交围栏和订单编号。
|
||||
- 不绕过拼多多登录、验证码、风险控制或支付边界。
|
||||
|
||||
## 执行记录
|
||||
|
||||
- 2026-07-30:冻结“单一采购需求/任务身份,多次受控 execution”的恢复契约;已产生
|
||||
下单围栏或下单状态不确定时 fail-closed。
|
||||
- 2026-07-30:新增 v20 `task_retries` 审计和 `TASK_REQUEUED` 事件;Admin API/SSR
|
||||
支持带幂等键和原因的原任务重新排队,SQLite 事务同时检查 execution outcome 与
|
||||
order submission 下单信号。
|
||||
- 2026-07-30:`go test ./...`、`go test -race ./...`、`go vet ./...` 和三个 Go
|
||||
入口构建通过;真实 Gin/SQLite + Playwright 验证 `CANCELED -> PENDING`、同一任务
|
||||
ID、重试历史和成功提示,1440px 与 390px 视口均无横向溢出。
|
||||
Reference in New Issue
Block a user