diff --git a/docs/tasks/T-202.md b/docs/tasks/T-202.md new file mode 100644 index 0000000..ac53125 --- /dev/null +++ b/docs/tasks/T-202.md @@ -0,0 +1,73 @@ +--- +id: T-202 +title: 手工建单与 DRAFT 基础列表 +phase: 2 +deps: [T-201, T-004, T-005] +status: DOING +created: 2026-08-04 +vikunja_task_id: 27 +context_ref: 1c35155 +work_branch: task/t-202-admin-draft +needs_device: false +needs_human_review: false +write_paths: + - docs/tasks/T-202.md + - admin/cmd/server/main.go + - admin/internal/config/** + - admin/internal/server/** + - admin/internal/tasks/** + - admin/internal/storage/sqlite/** + - admin/internal/transport/webui/** + - admin/README.md +--- + + +## 问题 / 背景 + +T-201 已提供管理员会话;T-004 已提供 tasks 表。根据 T-010 加速门禁,T-103 尚未完成时只允许实现不启动试选的 DRAFT 手工建单与基础列表。 + +## 关联需求与交互 + +F-001、US-001、IX-002;GET /tasks、GET /tasks/new、POST /tasks;沿用已确认的传统表格与创建弹窗/直达页。 + +## 方案 + +1. 显式数据库配置并打开已迁移 SQLite;以仓储接口隔离 HTTP 和 SQL,创建事务只写 MANUAL、DRAFT、version=1。 +2. 表单校验任务名称、canonical 拼多多链接、颜色分类、尺码、正整数数量和正十进制总额上限;金额只用字符串并规范为两位小数。链接只接受 HTTPS mobile.yangkeduo.com/goods.html 且 goods_id 为唯一纯数字参数,额外查询参数不进入数据库。 +3. 以服务端生成的 create_key 同时作为任务 ID;重复相同 key 和相同内容返回原结果,不创建第二条,内容不同则冲突。 +4. GET /tasks 默认 created_at DESC 显示 DRAFT 基础表格;创建入口用服务端渲染的 modal 状态,/tasks/new 复用同一表单作为无脚本兜底;失败保留非密码输入并显示字段错误,成功 303 回列表且新任务第一行。 +5. 页面只显示需求字段、采购结果占位、DRAFT 状态与创建时间;不读取或伪造规格面板价格/证据,不提供勾选开始试选、状态推进、详情或设备接口。 + +## 验收要点 + +- 覆盖创建成功、倒序第一行、严格链接/goods_id、数量、金额、空白/长度、CSRF/未登录、幂等重放与冲突、SQL 错误 fail closed。 +- 弹窗与 /tasks/new 共享校验;错误保留输入并可访问;标题只链接到由 goods_id 重建的 canonical PDD URL并使用安全新标签属性。 +- go test ./...、go test -race ./...、go vet ./...、go build ./...、完整 init.ps1、上下文校验和 diff-check 通过。 + +## 执行记录 + +(暂无) + + +## 边界 + +- 本任务只创建 `source=MANUAL`、`status=DRAFT`、`version=1` 的任务并显示 DRAFT 基础列表;不得 + 实现勾选、批量开始试选、`DRAFT → PENDING` 或任何其他状态流转,也不得新增设备领取接口。 +- 不增加或修改数据库 schema,不读写 `spec_trials`、`order_authorizations`、`order_submissions`, + 不生成或展示机器实际规格、规格面板单价、截图、证据哈希或 PDD 页面判据。 +- 启动服务必须从显式 `CMBUYER_DATABASE_SOURCE` 读取 SQLite data source;缺失时明确失败,不提供 + 隐式内存库或仓库内默认数据库。服务不自动猜迁移目录;README 必须先给出显式迁移命令。 +- 商品链接只接受 `https://mobile.yangkeduo.com/goods.html`,且必须恰有一个纯数字 `goods_id`; + 拒绝 userinfo、端口、fragment、重复参数、其他 host/scheme/path 和编码绕过。数据库只保存 goods_id, + 展示链接由 goods_id 重建 canonical URL;`uin` 等额外查询参数既不保存也不回显。 +- 标题、颜色分类、尺码必须去除首尾空白后非空并受明确长度上限约束;数量必须是可表示的正整数; + 总额上限必须是大于零、最多两位小数的十进制字符串并规范为两位小数。金额校验、保存与展示均不得 + 使用浮点数或从其他数字推测。 +- `create_key` 由服务端用 `crypto/rand` 生成并验证格式,同时作为任务 ID;相同 key 与相同规范化内容 + 重放只能返回原任务,不得二次 INSERT,相同 key 携带不同内容必须冲突。SQL 必须参数化,创建失败 + 不得留下半条或未知状态记录。 +- `GET /tasks`、`GET /tasks/new`、`POST /tasks` 都必须复用 T-201 管理会话;POST 必须验证 CSRF。 + 校验失败保留非敏感输入并逐字段提示,数据库内部错误只给通用响应,不泄露 SQL、路径或凭据。 +- 页面只使用服务端模板转义;标题商品链接在新标签打开时必须带 `noopener noreferrer`。导入按钮只作 + 禁用占位;不得加载外部资源或把原型假数据、真机数据、地址、手机号带进生产页面。 +- 不实现或引用试选、数量设置、订单确认、提交围栏、提交订单、付款、免密支付或先用后付能力。