--- id: T-203 title: 表格查询与批量开始采购授权 phase: 2 deps: [T-202, T-209] status: DONE created: 2026-08-04 vikunja_task_id: 30 context_ref: 1f20271 work_branch: task/t-203-start-purchases needs_device: false needs_human_review: false write_paths: - docs/tasks/T-203.md - admin/internal/auth/** - admin/internal/tasks/** - admin/internal/server/** - admin/internal/transport/webui/** - admin/internal/config/** - admin/cmd/server/** - admin/README.md --- ## 问题 / 背景 T-202 已完成手工 DRAFT 建单;T-209 将生产 schema/领域状态机迁移到单趟模型。项目所有者明确:管理员点击“开始采购(只创建待付款订单)”本身就是授权,不再增加试选后确认。T-203 负责采购服务查询和批量授权事务,使设备后续只能领取显式授权的 PENDING 任务。 ## 关联需求与交互 F-004、F-008、F-018;US-003、US-005;IX-005;GET /tasks、POST /tasks/start-purchases;依赖 T-202、T-209。 ## 方案 1. GET /tasks 支持 keyword、status、created_from、created_to;日期按 Asia/Shanghai 转为 UTC 半开区间,非法筛选返回可访问字段错误;默认全部状态并按 created_at DESC,rowid DESC。 2. 页面只让 DRAFT 行可勾选;表格上方显示选中数量、最高总额字符串合计、“系统不会付款”和唯一主按钮“开始采购(只创建待付款订单)”,不加逐行操作或重复确认弹窗。JS 只用同源静态文件,金额以分/BigInt 累计,不用浮点。 3. POST 使用服务端生成并渲染的 UUID v4 start_key,接收非空去重任务 id + expected_task_version;批量上限 100。created_by 只取已认证管理员,不接受请求字段。 4. 在一个有界 SQLite 写事务内先按 start_key 检查重放,再按 task_id 稳定排序读取并复核全部任务:存在、DRAFT、版本相等、锁定字段完整、数量/总额上限满足显式配置。任一失败整批不变。 5. 新请求为整批使用同一 created_at/expires_at;逐条创建 ACTIVE 一次性授权,锁定新 task_version、goods_id、颜色、尺码、数量和 total_price_cap;条件更新每条 DRAFT/version 为 PENDING/version+1,任一 RowsAffected != 1 则全批回滚。 6. 相同 start_key + 相同规范集合(输入顺序无关)返回原 authorization ids/版本/有效期,不再次改任务;同 key 子集、超集、不同版本或残缺集合返回 409。网络结果不明时前端冻结原 key/载荷,只允许原样重放。 7. 显式配置并启动时校验授权 TTL、最大任务数量、最大总额;金额只用规范十进制字符串。T-207 才关闭过期授权,T-203 只写 expires_at。 ## 验收要点 - 覆盖单条/100条成功,任务版本仅加一次、快照逐字段相等、管理员来自服务端、UTC 过期时间正确。 - 任一缺失/非 DRAFT/版本冲突/字段不完整/数量或金额超限/中途 SQL 失败均整批零修改。 - 同 key 同集合、倒序集合、并发重放返回同一结果;同 key 不同集合冲突;不同 key 并发抢同一版本仅一个成功。 - 未登录、CSRF、空/重复/畸形/超大请求、配置边界和数据库故障 fail closed,不泄露内部错误。 - UI 只有 DRAFT 可选;筛选、全选当前可见项、选择反馈、网络不明重放、焦点/aria-live/横向滚动与不付款文案有测试;无同义确认弹窗。 - go test ./...、go test -race ./...、go vet ./...、go build ./...、node --check 静态 JS、完整 init.ps1、上下文校验和 diff-check 通过。 ## 执行记录 ### 2026-08-04T09:16:39Z · ila 2026-08-04 开始 T-203:依赖 T-209 已完成并合入 main。主 agent 已完成开工前只读审计,冻结 v2 schema 启动校验、单进程 writeGate、start_key 规范集合重放、Asia/Shanghai 到 UTC 半开区间、julianday 查询及 big.Int 分金额边界;本地状态转 DOING,分支 task/t-203-start-purchases。 ### 2026-08-04T10:18:10Z · ila 2026-08-04 T-203 独立终审退回两项:未认证须按 docs/api.md 返回 401,已认证但 CSRF 错误才返回 403;原始 JSON 在 64KiB 边界内必须先做严格 UTF-8 校验。现有 auth.Manager 无法只读区分“有效管理会话 + 错 CSRF”与“无会话”,因此任务所有者批准把 write_paths 最小扩展为 admin/internal/auth/**,仅允许增加只读认证状态 API 及测试;不得创建/旋转会话或放宽 CSRF。修复、复审和完整门禁通过前 T-203 保持 DOING。 ### 2026-08-04T10:26:57Z · ila 2026-08-04 T-203 完成:提交 5dcff4b,合入主分支 03a067e。实现传统任务表格、筛选、DRAFT 批量勾选和“开始采购(只创建待付款订单)”授权;1/100 条在单一事务内生成 ACTIVE 快照并原子转 PENDING,相同 start_key 原集合稳定重放。独立终审两轮后修复未认证 401/CSRF 403 与原始 UTF-8 严格校验。主 agent 合入后完整 init(client 112 tests)、Go 全量/竞态/vet/build、Node 7 tests、上下文和 diff-check 全部通过;未实现领取、真机、提交订单或付款。 ## 边界 - 本任务只实现管理员任务查询与批量“开始采购”授权;不实现任务详情/截图、设备 Bearer 身份、领取/ 租约、purchase attempt API、提交围栏、结果调和、真机自动化、创建订单点击或付款。 - “开始采购(只创建待付款订单)”按钮本身就是明确授权,不得再增加同义确认弹窗,也不得把它拆回 试选后确认。按钮附近必须持续显示系统不付款;页面不得提供逐行“开始采购”操作列。 - 只有 `DRAFT` 行可以勾选;批量事务必须全有或全无。任一任务缺失、状态/版本变化、锁定字段非法、 配置超限、授权插入失败或条件更新未命中,都不得留下部分授权或部分 `PENDING`。 - 幂等重放必须先于 DRAFT 状态检查:相同 `start_key` 和相同规范任务集合只返回原结果,任务版本不得 再增加;同 key 的子集、超集、不同 expected version 或残缺授权集合一律冲突。网络结果不明只能 原样重放同一个 key 和载荷,不能生成新 key。 - 授权只锁定任务的新版本、goods_id、颜色、尺码、数量和 `total_price_cap`;不得写入观察单价或 `authorized_unit_price`。金额校验、配置比较和浏览器合计均使用十进制字符串/整数分,不用浮点。 - `created_by` 只能来自已认证管理员会话;POST 必须验证 CSRF。设备凭据不能调用本接口,本任务也不 新增设备接口。内部数据库错误不得回显 SQL、路径、配置值或凭据。 - 过期授权的关闭/重置属于 T-207;本任务只创建 `expires_at`。授权一旦进入 `FENCED`,本任务没有 释放、取消、重新授权或重试入口。 - 本任务不实现、不引用通用真机点击、`submit_order_once()` 或任何支付、免密支付、先用后付能力。