安全分流失败采购任务并继续只读核单 #262

Open
opened 2026-08-18 09:47:25 +08:00 by ila · 1 comment
Owner

基本信息

  • 类型:需求
  • 父级大工单:#96
  • 所属 MVP / 版本:#97 真实采购首单闭环(不支付)
  • 阶段:阶段 3/4,提交后只读核单与真实设备验收

要解决什么

采购任务在提交订单前失败时,现有“重新采购”可以安全创建新运行;但任务已经写入 task_runs.irreversible_action_at、随后只读核单失败并进入 reconcile_manual_review 后,同一入口只显示“安全条件阻止”,无法使用 #261 的多微信选择器兼容逻辑再次读取已经存在的待付款订单。

目标是在不增加按钮的前提下,让“重新采购”入口按不可逆历史安全分流:提交前失败仍重新采购;提交后失败只继续核单,绝不重新下单。

做什么 / 不做什么

  • 做:预检增加“只可继续核单”分类;为 reconcile_manual_review 增加保留原运行记录和不可逆标记的恢复事务;同一入口先处理只读核单任务;确认框明确区分重新采购和继续核单;补充回归测试。
  • 不做:不清除或修改 irreversible_action_at;不为已进入不可逆阶段的任务创建新采购运行;不调用采购 Adapter 或再次提交订单;不自动付款;不增加按钮;不修改 Admin、接口或数据库结构。

怎么做

  • 修改 client/src/task_models.py,让批量预检结果分别表达可重新采购任务、只读核单任务、类型过滤任务和其他安全阻止任务。
  • 修改 client/src/task_repository.py:历史无不可逆标记时沿用重新采购校验;历史有不可逆标记且最新运行、任务均处于 reconcile_manual_review 时允许进入只读核单分类;增加原子方法把任务和原运行的步骤恢复为 reconcile_purchase,保留原 attempt_id、确认快照和不可逆时间。
  • 修改 client/src/pdd_ui_event.py:同一“重新采购”入口的确认文案显示两类数量;执行时只读核单任务优先,并直接调用 PurchaseReconcileService,不经过 PurchaseTaskService;核单未形成成功闭环时停止剩余队列。
  • 补充 Repository、事件层和采购恢复测试,证明不可逆任务不会触发采购 Adapter、不会创建新运行、不会再次下单。

验收标准

  • 提交订单前失败且没有不可逆标记的采购任务仍可重新采购。
  • reconcile_manual_review 且历史有不可逆标记的任务可从同一入口进入“继续核单”。
  • 继续核单保留原 attempt_id、irreversible_action_at 和最终确认快照,不创建新的采购运行。
  • 继续核单只调用 PurchaseReconcileService,不会调用采购 Adapter、提交订单或付款。
  • 确认框明确显示重新采购和继续核单数量,不把继续核单描述为再次下单。
  • 批量任务先执行继续核单;任一核单不确定或上报失败时停止剩余任务。
  • 其他不可逆状态、未知状态、未发送 Outbox 和类型不符任务仍安全阻止或过滤。
  • Python 3.10 定向测试、Client 回归、语法检查和离屏 UI 冒烟完成。

怎么验证

从 client/ 执行:

C:/Python310/python.exe -m py_compile src/task_models.py src/task_repository.py src/pdd_ui_event.py
C:/Python310/python.exe -m pytest -q test/test_task_repository.py test/test_pdd_ui_event.py test/test_purchase_recovery.py
C:/Python310/python.exe -m pytest -q test
$env:QT_QPA_PLATFORM="offscreen"
C:/Python310/python.exe -c "from src.ui_main import MainWindow; from PyQt5.QtWidgets import QApplication; app=QApplication([]); w=MainWindow(); print('OK'); app.quit()"
Remove-Item Env:QT_QPA_PLATFORM

真机只允许对���存在不可逆标记、已经生成订单的任务执行一次只读核单,不得重新提交订单或触发付款。

风险和回退

主要风险是误把不可逆任务送入采购执行器,导致重复订单。实现必须用独立分类和独立执行分支,恢复事务不得清除不可逆标记;只读核单必须排在真正重新采购之前。回退时删除继续核单分类和恢复入口,恢复为安全阻止;历史不可逆数据保持不变。

## 基本信息 - 类型:需求 - 父级大工单:#96 - 所属 MVP / 版本:#97 真实采购首单闭环(不支付) - 阶段:阶段 3/4,提交后只读核单与真实设备验收 ## 要解决什么 采购任务在提交订单前失败时,现有“重新采购”可以安全创建新运行;但任务已经写入 `task_runs.irreversible_action_at`、随后只读核单失败并进入 `reconcile_manual_review` 后,同一入口只显示“安全条件阻止”,无法使用 #261 的多微信选择器兼容逻辑再次读取已经存在的待付款订单。 目标是在不增加按钮的前提下,让“重新采购”入口按不可逆历史安全分流:提交前失败仍重新采购;提交后失败只继续核单,绝不重新下单。 ## 做什么 / 不做什么 - 做:预检增加“只可继续核单”分类;为 `reconcile_manual_review` 增加保留原运行记录和不可逆标记的恢复事务;同一入口先处理只读核单任务;确认框明确区分重新采购和继续核单;补充回归测试。 - 不做:不清除或修改 `irreversible_action_at`;不为已进入不可逆阶段的任务创建新采购运行;不调用采购 Adapter 或再次提交订单;不自动付款;不增加按钮;不修改 Admin、接口或数据库结构。 ## 怎么做 - 修改 `client/src/task_models.py`,让批量预检结果分别表达可重新采购任务、只读核单任务、类型过滤任务和其他安全阻止任务。 - 修改 `client/src/task_repository.py`:历史无不可逆标记时沿用重新采购校验;历史有不可逆标记且最新运行、任务均处于 `reconcile_manual_review` 时允许进入只读核单分类;增加原子方法把任务和原运行的步骤恢复为 `reconcile_purchase`,保留原 `attempt_id`、确认快照和不可逆时间。 - 修改 `client/src/pdd_ui_event.py`:同一“重新采购”入口的确认文案显示两类数量;执行时只读核单任务优先,并直接调用 `PurchaseReconcileService`,不经过 `PurchaseTaskService`;核单未形成成功闭环时停止剩余队列。 - 补充 Repository、事件层和采购恢复测试,证明不可逆任务不会触发采购 Adapter、不会创建新运行、不会再次下单。 ## 验收标准 - [ ] 提交订单前失败且没有不可逆标记的采购任务仍可重新采购。 - [ ] `reconcile_manual_review` 且历史有不可逆标记的任务可从同一入口进入“继续核单”。 - [ ] 继续核单保留原 `attempt_id`、`irreversible_action_at` 和最终确认快照,不创建新的采购运行。 - [ ] 继续核单只调用 `PurchaseReconcileService`,不会调用采购 Adapter、提交订单或付款。 - [ ] 确认框明确显示重新采购和继续核单数量,不把继续核单描述为再次下单。 - [ ] 批量任务先执行继续核单;任一核单不确定或上报失败时停止剩余任务。 - [ ] 其他不可逆状态、未知状态、未发送 Outbox 和类型不符任务仍安全阻止或过滤。 - [ ] Python 3.10 定向测试、Client 回归、语法检查和离屏 UI 冒烟完成。 ## 怎么验证 从 `client/` 执行: ```powershell C:/Python310/python.exe -m py_compile src/task_models.py src/task_repository.py src/pdd_ui_event.py C:/Python310/python.exe -m pytest -q test/test_task_repository.py test/test_pdd_ui_event.py test/test_purchase_recovery.py C:/Python310/python.exe -m pytest -q test $env:QT_QPA_PLATFORM="offscreen" C:/Python310/python.exe -c "from src.ui_main import MainWindow; from PyQt5.QtWidgets import QApplication; app=QApplication([]); w=MainWindow(); print('OK'); app.quit()" Remove-Item Env:QT_QPA_PLATFORM ``` 真机只允许对���存在不可逆标记、已经生成订单的任务执行一次只读核单,不得重新提交订单或触发付款。 ## 风险和回退 主要风险是误把不可逆任务送入采购执行器,导致重复订单。实现必须用独立分类和独立执行分支,恢复事务不得清除不可逆标记;只读核单必须排在真正重新采购之前。回退时删除继续核单分类和恢复入口,恢复为安全阻止;历史不可逆数据保持不变。
Author
Owner

实施完成,待用户验收

最终实现

  • “重新采购”预检增加“只继续核单”独立分类。
  • 仅任务及最新运行均为 reconcile_manual_review、最新运行有不可逆标记且没有未发送 Outbox 时允许恢复核单。
  • 恢复事务保留原 attempt_id、确认快照和 irreversible_action_at,不创建新采购运行。
  • 只读核单任务优先执行,直接调用 PurchaseReconcileService;提交器注入禁止创建采购 Adapter 的工厂。
  • 核单不确定、准备状态变化或上报失败时停止后续真正的重新采购任务。
  • 确认框分别显示可重新采购和只继续核单数量;没有新增按钮。

本机只读检查确认 cg44 满足新的继续核单条件;实施和测试期间没有修改该任务数据。

验证结果

  • Python 语法检查:通过。
  • Repository、事件层、采购恢复定向测试:96 项通过。
  • 完整 Client 测试:511 项通过,2 项既有版本号断言失败(工作区版本 0.1.2,测试仍期待 0.1.0),与本工单无关。
  • 离屏界面冒烟:���出 OK。
  • 真机未验证;没有重新提交订单或触发付款。

提交与归档

  • 实现提交:5ea8aa3
  • 归档提交:9f6705a
  • 归档:docs/task/262-安全分流失败采购任务并继续只读核单.md

状态:待用户验收。

## 实施完成,待用户验收 ### 最终实现 - “重新采购”预检增加“只继续核单”独立分类。 - 仅任务及最新运行均为 `reconcile_manual_review`、最新运行有不可逆标记且没有未发送 Outbox 时允许恢复核单。 - 恢复事务保留原 `attempt_id`、确认快照和 `irreversible_action_at`,不创建新采购运行。 - 只读核单任务优先执行,直接调用 `PurchaseReconcileService`;提交器注入禁止创建采购 Adapter 的工厂。 - 核单不确定、准备状态变化或上报失败时停止后续真正的重新采购任务。 - 确认框分别显示可重新采购和只继续核单数量;没有新增按钮。 本机只读检查确认 `cg44` 满足新的继续核单条件;实施和测试期间没有修改该任务数据。 ### 验证结果 - Python 语法检查:通过。 - Repository、事件层、采购恢复定向测试:96 项通过。 - 完整 Client 测试:511 项通过,2 项既有版本号断言失败(工作区版本 `0.1.2`,测试仍期待 `0.1.0`),与本工单无关。 - 离屏界面冒烟:���出 `OK`。 - 真机未验证;没有重新提交订单或触发付款。 ### 提交与归档 - 实现提交:`5ea8aa3` - 归档提交:`9f6705a` - 归档:`docs/task/262-安全分流失败采购任务并继续只读核单.md` 状态:待用户验收。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: chengma/cmautobuy#262