用户要求“内部系统,简单易用,不要复杂门禁”,并授权更新本工单正文。本版收敛为人工点击回填、输入天数、自动扫描、小批提交、显示结果;不增加产品内审批、二次确认、后台任务系统或复杂配置。保留设备归属、冲突不覆盖、设备互斥和禁止支付/确认收货等必要边界。
本次只更新方案,不代表批准实施、原型确认、迁移或真机操作。代码核验基线 main fdd26af;Android 已发布分支 7ac1355 的互斥锁与历史缓存相关文件与此基线一致。原工单真机现象与积压数量是创建时记录,本轮未重新验证,不作为实时统计。
fdd26af
7ac1355
Gitea MCP 当前不可用,回退项目安全配置提供凭据的 Gitea API;未输出或写入凭据。
2026-09-08 用户提出:Agent 侧扫描 PDD「我的订单」,把收货地址含采购任务编号的订单的订单号与下单时间回填到采购任务。原话摘录:
本工单只做服务端接收与回填,Agent 端入口与扫描由后续工单实现。
目标:提供一个 Agent 可调用的批量回填接口,按地址后缀定位采购任务,写入 pdd_order_no 与 order_submitted_at,并把 order_result_unknown 推进到 order_created。
pdd_order_no
order_submitted_at
order_result_unknown
order_created
积压数据:
status n 有irreversible_at 有pdd_order_no order_result_unknown 21 21 0 order_created 3 3 3
21 个任务全部具备 irreversible_at,全部缺 pdd_order_no,正是本工单的回填对象。
irreversible_at
Admin 展示层已完全就绪,本工单不需要改前端:
web/src/views/goauto/purchase-tasks/index.vue
pddOrderNo
orderSubmittedAt
pdd_order_no LIKE
admin_query.go
缺口一:没有后缀反解函数
server/app/goauto/purchasecontract/contract.go:361 只有生成方向:
server/app/goauto/purchasecontract/contract.go:361
func AddressSuffix(taskID uint64) string { return fmt.Sprintf("_cg%d", taskID) }
无反解实现。
缺口二:Agent 没有可用的提交端点
现有 POST /api/agent/v1/purchase-tasks/:taskId/result 绑定在 attempt 生命周期上(需先 claim、start),离线批量回填无法使用。
POST /api/agent/v1/purchase-tasks/:taskId/result
缺口三:缺少独立的 Agent 回填写入路径
现有人工解除未知结果路径 ResolveUnknown 挂在 admin 路由下,handler.go 要求:
ResolveUnknown
handler.go
req.OperatorID = operatorID(c) if req.OperatorID == 0 { writeError(c, fail(CodeInvalidRequest, "无法识别当前操作人")) return }
需要 JWT 与 AuthCheckRole(),Agent 不具备,且不应为此放宽 admin 权限模型。
AuthCheckRole()
下单时间回落依据(实测):
任务 63 对照,irreversible_at 与页面读到的真实下单时间仅差 140 毫秒:
irreversible_at 2026-09-03 17:01:04.140 order_submitted_at 2026-09-03 17:01:04.000
结构上也一致:markOrderSubmitStarted 就在 submitOrderOnce() 之前一步。仅一个样本,记为假设。
markOrderSubmitStarted
submitOrderOnce()
_cg7
_cg72
AddressSuffix
models.PurchaseTask.SetStatus
后端功能不要求 UI 原型;以本方案、现有采购模型、manual.go、agent_history.go 及单元测试作为设计与核验依据。实现前确定请求/逐条响应和轻量并发处理方式,不引入新任务类型。
数据变更范围为订单字段、对应采购状态及必要状态元数据;并非“只写两个字段”。订单号只进入授权业务字段和响应,日志仅记录任务号、设备号、requestId、结果分类等脱敏元数据,沿任务保留规则快照关联;不记录完整地址、收件人、手机号、请求正文或原始树。
实施时必须更新线上 Android-Agent-API-Contract、Business-Rules-and-Glossary、Architecture-and-Code-Map:新增 Agent 回填入口、设备归属、未知状态解除和时间回落是长期事实变化。在线回读后按仓库流程同步并检查 docs 镜像,不直接编辑镜像。本次仅工单方案更新,不修改 Wiki。
保留原目录无关改动,必要时用干净工作区执行文档同步,不覆盖 docs/12-syb-erp-interface.md。
无阻塞依赖。后续工单(Agent 回填入口与扫描)依赖本工单的端点先行落地。
本工单已建单,用户明确要求暂不实施(原话「建工单,先别做」)。实施启动前需用户再次确认。
Codex 评审提出的四处代码事实纠错,已逐条在基线 main fdd26af 上独立验证,全部成立:
task.Status = status
syncPurchaseGuardSlots()
regexp
regexp.Compile("_cg(\d+)(?![0-9])")
invalid or unsupported Perl syntax: (?!
TaskHistoryCache
getSharedPreferences
PurchaseTaskStore
TaskExecutionMutex.tryAcquire
|| activeTask.get() == taskId
用户已确认「内部系统,简单易用,不要复杂门禁」为其本人对 Codex 的原话,评审版的精简方向成立。
本次仅调整工单方案,不实施。
290a17e
独立核验(worktree D:/OPC/goauto-worktrees/issue-241,基线 fdd26af):
D:/OPC/goauto-worktrees/issue-241
git diff --name-only fdd26af HEAD
ParseAddressSuffix
AddressSuffix(id) != suffix
_cg07
_cg+7
SetStatus
already_backfilled
UnknownResolveRequestID
backfill:<来源>:<UUIDv5>
go build ./...
go test -count=1 ./app/goauto/purchase/... ./app/goauto/purchasecontract/...
实施将订单号唯一性校验挂入 models.PurchaseTask.BeforeSave,影响 7 个既有 tx.Save 调用点(lifecycle.go:226/256/528、manual.go:83/169、reset.go:132、order_backfill.go:158),超出原工单声明范围。
models.PurchaseTask.BeforeSave
tx.Save
lifecycle.go:226/256/528
manual.go:83/169
reset.go:132
order_backfill.go:158
具体问题:
ErrPurchaseOrderNumberUsed
order_backfill.go:171
Cancel
lifecycle
lifecycle.go:378
order_submit_started
purchase_rule_setting
1787983600000_purchase_rules.go
Agent 侧 PurchaseOutboxUploader.flush() 中 markUploaded 只在 submit 成功后调用,失败条目留在 pending 队列并在下次 flush 重试。因此瞬时数据库失败(锁等待、超时)可自愈,不会丢单。审核初期基于「真单丢失」反对保留钩子的理由不成立,已撤回。
PurchaseOutboxUploader.flush()
markUploaded
submit
用户确认:方向一(同一订单号被多个任务使用)应当报错并提醒采购员人工处理,不得静默放过;并确认相关改动放入本工单一起完成,不拆分、不留半成品。
因此本工单范围追加:
BeforeSave
lifecycle.go
error_message
PurchaseOutboxUploader.flush() 对永久性业务错误也会无限重试(submit 抛出即停在该条,下次继续)。瞬时错误如此处理正确,但永久性业务冲突会卡死队列。属 Agent 侧问题,另行处理,不混入本工单。
已完成并推送,待验收。
分支 feat/241-order-backfill-endpoint(独立 worktree D:/OPC/goauto-worktrees/issue-241),基于 fdd26af。
feat/241-order-backfill-endpoint
290a17e feat(purchase): add agent order backfill endpoint (#241)
72b8b5d fix(purchase): preserve conflicting order results for manual resolution (#241)
ca7f768
482ba34
追加范围的实现:
*models.PurchaseOrderNumberUsedError
TaskID
Unwrap()
order_backfill.go
errors.Is
PURCHASE_ORDER_NUMBER_ALREADY_USED
retryable=false
PURCHASE_BACKFILL_ORDER_ALREADY_USED
errors.As
go test -count=1 ./app/goauto/...
git diff --check
git diff fdd26af HEAD --check
android/
web/
docs/12-syb-erp-interface.md
server/config/settings.yml
关键测试覆盖已逐项核对:SubmitResult(order_created) 撞冲突后状态为 order_result_unknown、StatusVersion 恰好 +1、pdd_order_no 为空、错误证据含对方任务号、下单时间与不可逆时间保留、attempt 标记失败并保留原始 order_created 结果类型、重复提交返回 Replayed 且版本不变、对方任务未被修改、全库 pdd_order_no 该值仅 1 条,并最终通过 ResolveUnknown 用正确订单号人工核销为 order_created——闭环成立。
SubmitResult(order_created)
StatusVersion
Replayed
实施阶段曾按指示直接编辑镜像 docs/08-agent-api-contract.md,这违反「先改线上 Wiki」的规则,责任在派工指令,实施方已在文中自行标注该偏离。审核阶段已补正:先更新线上页面并在线回读,再由 harness.py sync 从线上重建镜像,未反向覆盖 Wiki。
docs/08-agent-api-contract.md
harness.py sync
三个页面均已更新并回读:
Android-Agent-API-Contract
1f5ee1b29c66773fa571d862241b63f02dae283b
Business-Rules-and-Glossary
afb3eb896e41d9aea0f15ad2682ec70b7a3924ea
Architecture-and-Code-Map
d547c17924ac53422232ad9d6c55a34c8cd8d63c
harness.py sync 与 sync --check 均通过,全部镜像一致。
sync --check
origin/feat/241-order-backfill-endpoint
PurchaseOutboxUploader.flush() 对永久性业务错误也会无限重试(submit 抛出即停在该条,下次继续)。瞬时错误如此处理正确,但永久性业务冲突会卡死上传队列。属 Agent 侧,建议并入 #242 或另建工单。
No dependencies set.
The note is not visible to the blocked user.
2026-09-08 评审更新:内部系统精简方案
用户要求“内部系统,简单易用,不要复杂门禁”,并授权更新本工单正文。本版收敛为人工点击回填、输入天数、自动扫描、小批提交、显示结果;不增加产品内审批、二次确认、后台任务系统或复杂配置。保留设备归属、冲突不覆盖、设备互斥和禁止支付/确认收货等必要边界。
本次只更新方案,不代表批准实施、原型确认、迁移或真机操作。代码核验基线 main
fdd26af;Android 已发布分支7ac1355的互斥锁与历史缓存相关文件与此基线一致。原工单真机现象与积压数量是创建时记录,本轮未重新验证,不作为实时统计。Gitea MCP 当前不可用,回退项目安全配置提供凭据的 Gitea API;未输出或写入凭据。
来源与目标
2026-09-08 用户提出:Agent 侧扫描 PDD「我的订单」,把收货地址含采购任务编号的订单的订单号与下单时间回填到采购任务。原话摘录:
本工单只做服务端接收与回填,Agent 端入口与扫描由后续工单实现。
目标:提供一个 Agent 可调用的批量回填接口,按地址后缀定位采购任务,写入
pdd_order_no与order_submitted_at,并把order_result_unknown推进到order_created。当前事实(2026-09-08 核验)
积压数据:
21 个任务全部具备
irreversible_at,全部缺pdd_order_no,正是本工单的回填对象。Admin 展示层已完全就绪,本工单不需要改前端:
web/src/views/goauto/purchase-tasks/index.vue列表列「订单 / 支付」已显示pddOrderNo,空值占位「尚未取得订单号」pddOrderNo与orderSubmittedAtpdd_order_no LIKE搜索admin_query.go已返回这两个字段缺口一:没有后缀反解函数
server/app/goauto/purchasecontract/contract.go:361只有生成方向:无反解实现。
缺口二:Agent 没有可用的提交端点
现有
POST /api/agent/v1/purchase-tasks/:taskId/result绑定在 attempt 生命周期上(需先 claim、start),离线批量回填无法使用。缺口三:缺少独立的 Agent 回填写入路径
现有人工解除未知结果路径
ResolveUnknown挂在 admin 路由下,handler.go要求:需要 JWT 与
AuthCheckRole(),Agent 不具备,且不应为此放宽 admin 权限模型。下单时间回落依据(实测):
任务 63 对照,
irreversible_at与页面读到的真实下单时间仅差 140 毫秒:结构上也一致:
markOrderSubmitStarted就在submitOrderOnce()之前一步。仅一个样本,记为假设。范围
_cg7与_cg72;拒绝零、溢出和歧义。不得照搬 Go 不支持的负向前瞻正则,可完整解析后比较既有AddressSuffix。请求不含地址全文。order_result_unknown回填为order_created;已为order_created且订单号一致视为已回填、不重复更新。其他状态明确拒绝,不自动把运行中、失败、取消或演练任务改成功。models.PurchaseTask.SetStatus仅设置状态并同步 guard slots,不检查旧状态到新状态的合法转换。新服务负责来源状态校验,再复用 SetStatus;同一事务更新订单字段、statusVersion、statusChangedAt,清理已解除的主任务当前错误,保留历史 attempt 与原始执行结果,不伪造一次采购执行。支付、物流、SYB 回填状态不变。非目标
ResolveUnknown及 admin 权限模型。pdd_order_no与order_submitted_at两个订单字段(2026-09-08 用户确认)。不同步 PDD 订单的商品、金额、订单状态、物流或其他任何信息。此处的“两个字段”指业务回填范围;实现上仍需同步更新采购状态与必要状态元数据(见范围第 5 条),二者不矛盾。方案与设计证据
后端功能不要求 UI 原型;以本方案、现有采购模型、manual.go、agent_history.go 及单元测试作为设计与核验依据。实现前确定请求/逐条响应和轻量并发处理方式,不引入新任务类型。
数据变更范围为订单字段、对应采购状态及必要状态元数据;并非“只写两个字段”。订单号只进入授权业务字段和响应,日志仅记录任务号、设备号、requestId、结果分类等脱敏元数据,沿任务保留规则快照关联;不记录完整地址、收件人、手机号、请求正文或原始树。
验收
_cg7/_cg72、非法后缀、零/溢出与歧义测试通过。文档影响
实施时必须更新线上 Android-Agent-API-Contract、Business-Rules-and-Glossary、Architecture-and-Code-Map:新增 Agent 回填入口、设备归属、未知状态解除和时间回落是长期事实变化。在线回读后按仓库流程同步并检查 docs 镜像,不直接编辑镜像。本次仅工单方案更新,不修改 Wiki。
保留原目录无关改动,必要时用干净工作区执行文档同步,不覆盖 docs/12-syb-erp-interface.md。
依赖与后续
无阻塞依赖。后续工单(Agent 回填入口与扫描)依赖本工单的端点先行落地。
实施分工
本工单已建单,用户明确要求暂不实施(原话「建工单,先别做」)。实施启动前需用户再次确认。
2026-09-08 用户确认后的调整(Claude 复核 Codex 评审版)
Codex 评审提出的四处代码事实纠错,已逐条在基线 main
fdd26af上独立验证,全部成立:models.PurchaseTask.SetStatus确实只做task.Status = status加syncPurchaseGuardSlots(),不校验来源状态到目标状态的合法性。原工单「复用既有 SetStatus 校验」的表述是错的,已由评审版修正。regexp(RE2) 确实不支持负向前瞻,实测regexp.Compile("_cg(\d+)(?![0-9])")报invalid or unsupported Perl syntax: (?!。原工单直接搬用了 Kotlin 侧写法,是错的。TaskHistoryCache使用getSharedPreferences,不是 SQLite;SQLite 的是PurchaseTaskStore。TaskExecutionMutex.tryAcquire含|| activeTask.get() == taskId分支,对同一 ID 可重入。固定预留 ID 确实防不住重复启动,必须另加原子防重入标记。用户已确认「内部系统,简单易用,不要复杂门禁」为其本人对 Codex 的原话,评审版的精简方向成立。
本次调整
pdd_order_no与order_submitted_at两个订单字段,不同步商品、金额、订单状态、物流等其他信息(用户 2026-09-08 明确确认)。已撤回的意见
本次仅调整工单方案,不实施。
2026-09-08 Claude 审核
290a17e+ 用户确认追加范围审核通过的部分
独立核验(worktree
D:/OPC/goauto-worktrees/issue-241,基线fdd26af):git diff --name-only fdd26af HEAD无 android/ 或 web/ 命中。ParseAddressSuffix用AddressSuffix(id) != suffix回比替代负向前瞻,天然拒绝_cg07、_cg+7等非规范形式;测试覆盖_cg7/_cg72区分与 16 个拒绝用例(含前导零、溢出、全角数字、内嵌、首尾空白)。SetStatus(符合评审版指出的代码事实)。already_backfilled幂等、时间回落与来源标记均已实现。UnknownResolveRequestID槽写入backfill:<来源>:<UUIDv5>,记录来源且无需迁移。go build ./...、go test -count=1 ./app/goauto/purchase/... ./app/goauto/purchasecontract/...强制重跑通过。审核发现
实施将订单号唯一性校验挂入
models.PurchaseTask.BeforeSave,影响 7 个既有tx.Save调用点(lifecycle.go:226/256/528、manual.go:83/169、reset.go:132、order_backfill.go:158),超出原工单声明范围。具体问题:
ErrPurchaseOrderNumberUsed仅在order_backfill.go:171映射为业务错误码,ResolveUnknown/Cancel/lifecycle三条路径无映射,会向管理员暴露裸 Go 错误。lifecycle.go:378的order_created结果回传路径处于不可逆边界之后(真单已创建)。该处校验失败会导致整笔结果提交失败、任务滞留order_submit_started、订单号丢失——正是 #210 已修复的故障形态。purchase_rule_setting成为订单号分配的全局互斥锁,存在隐性耦合;单例行缺失时 fail-closed 将影响所有带订单号的保存。该行由迁移1787983600000_purchase_rules.go种下,当前库存在,实际风险低。已核实、推翻的担忧
Agent 侧
PurchaseOutboxUploader.flush()中markUploaded只在submit成功后调用,失败条目留在 pending 队列并在下次 flush 重试。因此瞬时数据库失败(锁等待、超时)可自愈,不会丢单。审核初期基于「真单丢失」反对保留钩子的理由不成立,已撤回。用户决策与追加范围
用户确认:方向一(同一订单号被多个任务使用)应当报错并提醒采购员人工处理,不得静默放过;并确认相关改动放入本工单一起完成,不拆分、不留半成品。
因此本工单范围追加:
BeforeSave全局唯一性校验(本次经用户确认,不再视为超范围)。ErrPurchaseOrderNumberUsed必须在ResolveUnknown、Cancel、lifecycle三条路径映射为明确业务错误码,错误信息需指明冲突对方任务号,供采购员定位。不得暴露裸 Go 错误。lifecycle.go的order_created路径撞到订单号冲突时必须降级,不得整笔失败:将任务置为order_result_unknown,并在error_message写明「读到订单号 X,但该号已属于任务 CG-yy」。理由是该时刻真单已在 PDD 创建,首要目标是保住「订单已存在」这一事实并交由人工核对;order_result_unknown+ admin「处理订单结果未知」弹窗正是既有的人工处理通道,无需新增界面。order_created冲突降级后的状态与error_message内容。记录:相邻问题,不在本工单处理
PurchaseOutboxUploader.flush()对永久性业务错误也会无限重试(submit抛出即停在该条,下次继续)。瞬时错误如此处理正确,但永久性业务冲突会卡死队列。属 Agent 侧问题,另行处理,不混入本工单。已完成并推送,待验收。
实现
分支
feat/241-order-backfill-endpoint(独立 worktreeD:/OPC/goauto-worktrees/issue-241),基于fdd26af。290a17e feat(purchase): add agent order backfill endpoint (#241)—— 端点主体、后缀反解、逐条事务与结果、幂等、时间回落。72b8b5d fix(purchase): preserve conflicting order results for manual resolution (#241)—— 审核后追加范围。ca7f768/482ba34—— Wiki 镜像同步。追加范围的实现:
*models.PurchaseOrderNumberUsedError,携带冲突对方TaskID,并实现Unwrap()返回原 sentinel,故order_backfill.go既有errors.Is判定不受影响。PURCHASE_ORDER_NUMBER_ALREADY_USED,覆盖ResolveUnknown、Cancel、lifecycle三条路径,HTTP 409、retryable=false,消息含冲突对方任务号。回填路径继续使用原有PURCHASE_BACKFILL_ORDER_ALREADY_USED。lifecycle.go的order_created路径改为降级而非失败:仅当错误确为订单号冲突时(errors.As判定,其他错误仍硬失败)把任务降为order_result_unknown,pdd_order_no留空以维持唯一性,冲突号以「读到订单号 X,但该号已属于任务 CG-yy」写入任务与 attempt 的error_message,保留order_submitted_at与irreversible_at,释放租约与运行槽。审核验证(Claude 独立执行)
go build ./...、go test -count=1 ./app/goauto/...强制重跑通过。git diff --check、git diff fdd26af HEAD --checkclean。git diff --name-only fdd26af HEAD无android/或web/命中;290a17e哈希未被改写。docs/12-syb-erp-interface.md、server/config/settings.yml及未跟踪文件)全程未被触碰——本工单在独立 worktree 中完成,正是为此。关键测试覆盖已逐项核对:
SubmitResult(order_created)撞冲突后状态为order_result_unknown、StatusVersion恰好 +1、pdd_order_no为空、错误证据含对方任务号、下单时间与不可逆时间保留、attempt 标记失败并保留原始order_created结果类型、重复提交返回Replayed且版本不变、对方任务未被修改、全库pdd_order_no该值仅 1 条,并最终通过ResolveUnknown用正确订单号人工核销为order_created——闭环成立。文档(Wiki-first 已补正)
实施阶段曾按指示直接编辑镜像
docs/08-agent-api-contract.md,这违反「先改线上 Wiki」的规则,责任在派工指令,实施方已在文中自行标注该偏离。审核阶段已补正:先更新线上页面并在线回读,再由harness.py sync从线上重建镜像,未反向覆盖 Wiki。三个页面均已更新并回读:
Android-Agent-API-Contractrevision1f5ee1b29c66773fa571d862241b63f02dae283bBusiness-Rules-and-Glossaryrevisionafb3eb896e41d9aea0f15ad2682ec70b7a3924eaArchitecture-and-Code-Maprevisiond547c17924ac53422232ad9d6c55a34c8cd8d63charness.py sync与sync --check均通过,全部镜像一致。未验证 / 未执行
origin/feat/241-order-backfill-endpoint。相邻问题(已记录,不在本工单处理)
PurchaseOutboxUploader.flush()对永久性业务错误也会无限重试(submit抛出即停在该条,下次继续)。瞬时错误如此处理正确,但永久性业务冲突会卡死上传队列。属 Agent 侧,建议并入 #242 或另建工单。