--- id: T-243 title: 允许货运删除清理未完成采购需求草稿 phase: 2 deps: - T-242 status: DONE created: 2026-07-29 context_ref: e5f403f work_branch: null write_paths: - docs/tasks/T-243.md - docs/current-state.md - backend-api/internal/usecase/** - backend-api/internal/repository/sqlite/freight_* - backend-api/internal/transport/httpapi/** - backend-api/internal/transport/webui/** --- ## Bug / 背景 T-242 把任意 `procurement_requests` 记录都视为“已进入采购流程”。采购人员点击“创建采购 需求”后,系统会先生成 `NEEDS_IMAGE` 需求,但只有上传参考图并显式生成后才会出现 `purchase_tasks`。因此无参考图、无任务的需求既不会显示在采购任务列表,又会阻止货运删除, 用户无法从界面完成清理。 这不是采购任务列表漏查:任务列表应继续只展示真实 `purchase_tasks`。Bug 是删除保护边界把 采购准备草稿和不可丢失的参考图/采购任务混为一谈,且冲突提示没有说明实际阻止原因。 ## 修订契约 - `reference_asset_id IS NULL` 且 `purchase_task_id IS NULL` 的采购需求属于可清理草稿。 - 可清理草稿只允许处于 `BLOCKED`、`NEEDS_IMAGE` 或 `SOURCE_CHANGED`;删除货运单时在同一 事务内一并删除。 - 任一需求已绑定参考图但尚未生成任务时,整单禁止删除并返回 `FREIGHT_ORDER_HAS_REFERENCE`。 - 任一需求已生成采购任务时,整单禁止删除并优先返回 `FREIGHT_ORDER_HAS_PURCHASE_TASK`。 - 货运详情把 `NEEDS_IMAGE` 显示为“需要参考图,尚未生成任务”,避免与采购任务列表混淆。 ## 方案 1. 用两个来源中立仓储错误替换过宽的 `ErrFreightOrderInUse`,由 usecase 映射为两个稳定 `409` 错误码;若同单同时存在参考图和任务,优先报告采购任务。 2. SQLite immediate transaction 先检查全部历史商品关联:任何非空 `purchase_task_id` 立即回滚;其次任何非空 `reference_asset_id` 立即回滚。 3. 无保护性关联时,删除该订单商品下状态为 `BLOCKED`、`NEEDS_IMAGE`、`SOURCE_CHANGED` 且两个关联 ID 均为空的采购需求,再沿用 T-242 的图片键收集、商品和订单删除。 4. 不依赖状态字符串来放宽保护:无法归类、数据不一致或未来新增状态的需求保持 fail-closed, 由剩余外键阻止删除并映射内部错误。 5. SSR 对两类冲突使用独立提示;确认弹窗明确未绑定参考图的草稿需求会一并删除,已有参考图 或任务时系统拒绝。API 保持相同 DELETE 路由和幂等 204。 6. 仓储、usecase、API 与 Web 测试覆盖草稿删除、参考图保护、任务保护、优先级、事务不变性 和现有无需求删除;真实响应式 smoke 使用脱敏隔离数据,不操作线上 ERP。 ## 验收要点 - [x] 仅含 `NEEDS_IMAGE` 且无参考图/任务的货运单可删除,草稿需求、商品和订单同事务消失。 - [x] `BLOCKED` 及无参考图的 `SOURCE_CHANGED` 草稿遵守相同删除规则。 - [x] 已绑定参考图但未生成任务返回 409 `FREIGHT_ORDER_HAS_REFERENCE`,全部记录不变化。 - [x] 已生成采购任务返回 409 `FREIGHT_ORDER_HAS_PURCHASE_TASK`,全部采购链路不变化。 - [x] 同单同时存在两类保护关联时优先报告已生成采购任务。 - [x] 未知或不一致需求不能被静默删除。 - [x] 采购任务列表仍只展示 `purchase_tasks`;货运详情准确说明需求尚未生成任务。 - [x] Web 成功、参考图冲突和任务冲突反馈明确,CSRF、确认和响应式交互不回归。 - [x] 删除不影响 ERP、同步历史、水位和 T-242 图片文件清理。 - [x] 标准 Go 测试、race、vet 和三个入口构建通过。 ## 边界 - 不把采购需求加入采购任务列表,不新增第二套采购列表。 - 不删除已绑定参考图、采购任务、执行、候选、授权、下单或支付记录。 - 不增加采购需求单独撤销、批量删除、回收站或归档。 - 不修改顺运宝 ERP,不回退同步水位。 - 不在文档、测试、日志或提交中写入真实订单、商品、店铺或图片。 ## 执行记录 - 2026-07-29:根据真实本地状态审计确认,删除冲突来自 `NEEDS_IMAGE` 采购需求,且其 `reference_asset_id`、`purchase_task_id` 均为空;采购任务列表只查 `purchase_tasks` 的行为正确。本任务修复保护边界和反馈,不改变任务列表定义。 - 2026-07-29:SQLite immediate 事务先检查 `purchase_task_id`,再检查 `reference_asset_id`;两者均不存在时只删除 `BLOCKED`、`NEEDS_IMAGE`、 `SOURCE_CHANGED` 草稿,并在删除商品前确认没有其他需求残留。任务冲突优先于参考图 冲突,未来或不一致状态保持 fail-closed;无需 schema migration。 - 2026-07-29:Admin API 保持原 DELETE 路由和幂等 204,保护分支分别返回 `FREIGHT_ORDER_HAS_REFERENCE`、`FREIGHT_ORDER_HAS_PURCHASE_TASK`。SSR 使用独立提示, 详情把 `NEEDS_IMAGE` 显示为“需要参考图,尚未生成任务”,确认文案明确草稿会被清理。 - 2026-07-29:仓储测试覆盖三种草稿状态、参考图保护、任务保护和任务优先级;usecase、 API、SSR 测试覆盖稳定错误映射及实际 `NEEDS_IMAGE` 删除。`go test ./...`、 `go test -race ./...`、`go vet ./...` 和 API、authctl、migrate 三个入口构建通过。 Playwright 连接脱敏隔离 Gin/SQLite,以桌面和 390×844 验证状态说明、确认弹窗、真实 登录及删除后空列表,控制台无警告或错误;未访问顺运宝 ERP。