feat: 采购管理按设备、按创建人筛选,记录采购任务创建人与来源 #382

Open
opened 2026-10-12 16:51:12 +08:00 by ila · 3 comments
Owner

基本信息

  • 类型:功能(服务端 + Web:Admin 采购管理列表新增「按设备」「按创建人」筛选,记录采购任务创建人与创建来源)。
  • 状态:已实现并推送分支,待合并与发布(2026-10-12)。含数据库迁移,执行迁移与发布需用户授权。
  • 建单:用户「两张都要建」;Gitea MCP 不可用,回退 REST。
  • 来源:2026-10-12 用户:「现在admin采购管理页数据需要增加按设备筛选和按采购员账号筛选,这两个筛选条件。你觉得合理吗」→ 明确「采购员账号」为「创建采购任务的admin账号」→ 确认:「1,可以;2,接受.起草工单」(1 = Agent 发起的任务创建人为空、来源记为 Agent;2 = 历史任务显示「未记录」,不回填)。
  • 关联:#372(采购管理导出按当前筛选)、#237(客户端密钥接口)、#367(Agent 最后一批重购)、Agent 重试采购。

已核验事实(origin/main 16e3d35,2026-10-12)

  • purchase_task.device_id 已有索引(models/purchase.go:89),列表已显示设备名(web/src/views/goauto/purchase-tasks/index.vue:32「尚未分派」)。
  • purchase_task 没有创建人字段;只有 spec_decision_by、payment_reviewed_by、re_purchase_authorized_by、cancelled_by 等其他动作的操作人。
  • 列表接口 GET /api/admin/v1/purchase-tasks(purchase/handler.go:24 AdminList → admin_query.go:17 AdminListRequest),现有筛选:任务、类型、状态、执行模式、SYB 商品、蝦皮订单号(含多行)、PDD 订单号、下单日期。允许角色 admin / purchaser / after_sales 及客户端密钥(allowedOperator)。
  • 操作人取值 operatorID(c)(handler.go:553):JWT identity;客户端密钥请求取 AuthorizedBy(= 密钥的 UpdatedBy,clientapi/gateway.go:82)。
  • 采购任务创建入口:Admin 单个创建 Create(service.go:71)、备货 CreateStock(:365)、批量 BatchCreate(batch.go:272→Create)、Agent 重试 AgentRetry(retry.go:218→batchRetryCreate→Create,Device Token,无 Admin 账号)及 Agent 最后一批重购(同走 Agent 重试路径)。
  • Web 采购管理页筛选区 index.vue:7–20,导出按当前筛选(#372)。

目标

  1. 列表可按设备筛选(含「未分派」)。
  2. 新任务记录创建人(Admin 账号)与创建来源,列表可按创建人筛选,并显示「创建人」列。

非目标

  • 不回填历史任务的创建人(用户接受显示「未记录」);不从 sys_opera_log 反推。
  • 不改权限与可见范围:采购员仍可看到全部任务,筛选只是筛选。
  • 不改 Android、Agent API、采购状态机、设备分派与互斥。
  • 导出列不变(导出沿用当前筛选,自然包含新筛选条件)。

方案

  1. 迁移:purchase_task 新增可空列 created_by(bigint unsigned,索引)与 created_source(varchar(16),非空默认 '';取值 admin / client_api / agent,空串表示历史未记录)。只新增列,不改已有数据。
  2. 写入(只在新建任务时写一次,之后不更新):
    • Admin 单个、备货、批量创建:created_by = 当前 JWT 用户,created_source=admin;
    • (实施更正)Admin 批量重试 BatchRetry 实为原地重置原任务(Reset,任务号不变),不新建任务,因此不写创建人;
    • 客户端密钥请求:created_by = AuthorizedBy,created_source=client_api;
    • Agent 重试 / 重购:created_by 为空,created_source=agent(设备由既有 device_id 体现)。
    • 幂等重放(同 requestId)返回原任务,不改写创建人。
  3. 列表接口:AdminListRequest 新增 deviceId(数字,或 unassigned 表示 device_id IS NULL)与 createdBy(数字,或 agent 表示 created_source='agent',unrecorded 表示 created_source='');列表项新增 createdBy、createdByName、createdSource。
  4. 筛选选项接口:新增 GET /api/admin/v1/purchase-tasks/filter-options,同样受 allowedOperator 保护,返回采购任务中实际出现过的设备(id、名称)与创建人(id、显示名),避免为采购员开放设备或用户管理接口。
  5. Web(purchase-tasks/index.vue、api/goauto/purchase-tasks.js):筛选区新增「设备」「创建人」两个可清空下拉(含「未分派」/「Agent 发起」/「未记录」选项),参与查询、重置与导出;表格新增「创建人」列(显示名,Agent 发起显示「Agent」,历史显示「未记录」);详情页同步显示创建人与来源。复用现有 el-select 与表格列样式。

设计证据

现有页面小范围变化:筛选区追加两个与「类型」「状态」同样式的下拉,表格在「设备」列后追加一列「创建人」,详情描述追加一项;复用现有组件与样式,无新页面、无布局重构,不需要原型。

子项目影响、依赖与并行

  • Server:迁移(server/cmd/migrate/migration/version-local/)、models/purchase.go、purchase/(service、batch、retry、admin_query、handler、router)及测试。
  • Web:views/goauto/purchase-tasks/index.vue、api/goauto/purchase-tasks.js。
  • Android:无。共享契约:Admin API 文档(Architecture-and-Code-Map / 相关页)更新列表参数与字段。
  • 无前置依赖;与 #381(SYB 已取消订单)都改 purchase/ 包但不同文件区域,建议串行合并。

验收标准

  • 迁移在空库与存量库上执行成功;旧行 created_by 为 NULL、created_source 为空。
  • 单元测试:各创建入口写入正确的创建人与来源;幂等重放不改写;Agent 重试来源为 agent 且创建人为空。
  • 单元测试:deviceId(含 unassigned)、createdBy(含 agent、unrecorded)筛选及与现有筛选组合;非法值返回 400;filter-options 只返回实际出现的设备与创建人,权限与列表一致。
  • Web:两个筛选可用并参与重置与导出;「创建人」列与详情显示正确;前端构建与相关测试通过。
  • 发布(含迁移)需用户另行授权。

风险与回退

  • 数据库迁移(新增列与索引):高风险类别,实施和发布前再次确认;只新增可空列,不锁改既有数据。
  • 历史任务无创建人,按创建人筛选只覆盖上线后的新任务。
  • 回退:回滚代码即可(新增列保留不影响旧代码);如需删除列另行评估。

文档影响

  • 更新 Wiki Architecture-and-Code-Map(Admin 采购任务列表参数、filter-options 接口、created_by/created_source 字段)与 Business-Rules-and-Glossary(创建人与来源的记录规则、历史显示「未记录」、Agent 发起无创建人)。
  • 先改线上 Wiki 并回读 revision,再执行一次 sync 与 sync --check。
## 基本信息 - 类型:功能(服务端 + Web:Admin 采购管理列表新增「按设备」「按创建人」筛选,记录采购任务创建人与创建来源)。 - 状态:**已实现并推送分支,待合并与发布**(2026-10-12)。含数据库迁移,执行迁移与发布需用户授权。 - 建单:用户「两张都要建」;Gitea MCP 不可用,回退 REST。 - 来源:2026-10-12 用户:「现在admin采购管理页数据需要增加按设备筛选和按采购员账号筛选,这两个筛选条件。你觉得合理吗」→ 明确「采购员账号」为「创建采购任务的admin账号」→ 确认:「1,可以;2,接受.起草工单」(1 = Agent 发起的任务创建人为空、来源记为 Agent;2 = 历史任务显示「未记录」,不回填)。 - 关联:#372(采购管理导出按当前筛选)、#237(客户端密钥接口)、#367(Agent 最后一批重购)、Agent 重试采购。 ## 已核验事实(`origin/main` `16e3d35`,2026-10-12) - `purchase_task.device_id` 已有索引(`models/purchase.go:89`),列表已显示设备名(`web/src/views/goauto/purchase-tasks/index.vue:32`「尚未分派」)。 - `purchase_task` **没有创建人字段**;只有 `spec_decision_by`、`payment_reviewed_by`、`re_purchase_authorized_by`、`cancelled_by` 等其他动作的操作人。 - 列表接口 `GET /api/admin/v1/purchase-tasks`(`purchase/handler.go:24` `AdminList` → `admin_query.go:17` `AdminListRequest`),现有筛选:任务、类型、状态、执行模式、SYB 商品、蝦皮订单号(含多行)、PDD 订单号、下单日期。允许角色 admin / purchaser / after_sales 及客户端密钥(`allowedOperator`)。 - 操作人取值 `operatorID(c)`(`handler.go:553`):JWT `identity`;客户端密钥请求取 `AuthorizedBy`(= 密钥的 `UpdatedBy`,`clientapi/gateway.go:82`)。 - 采购任务创建入口:Admin 单个创建 `Create`(`service.go:71`)、备货 `CreateStock`(`:365`)、批量 `BatchCreate`(`batch.go:272`→`Create`)、Agent 重试 `AgentRetry`(`retry.go:218`→`batchRetryCreate`→`Create`,Device Token,无 Admin 账号)及 Agent 最后一批重购(同走 Agent 重试路径)。 - Web 采购管理页筛选区 `index.vue:7–20`,导出按当前筛选(#372)。 ## 目标 1. 列表可按设备筛选(含「未分派」)。 2. 新任务记录创建人(Admin 账号)与创建来源,列表可按创建人筛选,并显示「创建人」列。 ## 非目标 - 不回填历史任务的创建人(用户接受显示「未记录」);不从 `sys_opera_log` 反推。 - 不改权限与可见范围:采购员仍可看到全部任务,筛选只是筛选。 - 不改 Android、Agent API、采购状态机、设备分派与互斥。 - 导出列不变(导出沿用当前筛选,自然包含新筛选条件)。 ## 方案 1. **迁移**:`purchase_task` 新增可空列 `created_by`(bigint unsigned,索引)与 `created_source`(varchar(16),非空默认 `''`;取值 `admin` / `client_api` / `agent`,空串表示历史未记录)。只新增列,不改已有数据。 2. **写入**(只在新建任务时写一次,之后不更新): - Admin 单个、备货、批量创建:`created_by` = 当前 JWT 用户,`created_source=admin`; - (实施更正)Admin 批量重试 `BatchRetry` 实为原地重置原任务(`Reset`,任务号不变),不新建任务,因此不写创建人; - 客户端密钥请求:`created_by` = `AuthorizedBy`,`created_source=client_api`; - Agent 重试 / 重购:`created_by` 为空,`created_source=agent`(设备由既有 `device_id` 体现)。 - 幂等重放(同 `requestId`)返回原任务,不改写创建人。 3. **列表接口**:`AdminListRequest` 新增 `deviceId`(数字,或 `unassigned` 表示 `device_id IS NULL`)与 `createdBy`(数字,或 `agent` 表示 `created_source='agent'`,`unrecorded` 表示 `created_source=''`);列表项新增 `createdBy`、`createdByName`、`createdSource`。 4. **筛选选项接口**:新增 `GET /api/admin/v1/purchase-tasks/filter-options`,同样受 `allowedOperator` 保护,返回采购任务中实际出现过的设备(id、名称)与创建人(id、显示名),避免为采购员开放设备或用户管理接口。 5. **Web**(`purchase-tasks/index.vue`、`api/goauto/purchase-tasks.js`):筛选区新增「设备」「创建人」两个可清空下拉(含「未分派」/「Agent 发起」/「未记录」选项),参与查询、重置与导出;表格新增「创建人」列(显示名,Agent 发起显示「Agent」,历史显示「未记录」);详情页同步显示创建人与来源。复用现有 `el-select` 与表格列样式。 ## 设计证据 现有页面小范围变化:筛选区追加两个与「类型」「状态」同样式的下拉,表格在「设备」列后追加一列「创建人」,详情描述追加一项;复用现有组件与样式,无新页面、无布局重构,不需要原型。 ## 子项目影响、依赖与并行 - Server:迁移(`server/cmd/migrate/migration/version-local/`)、`models/purchase.go`、`purchase/`(service、batch、retry、admin_query、handler、router)及测试。 - Web:`views/goauto/purchase-tasks/index.vue`、`api/goauto/purchase-tasks.js`。 - Android:无。共享契约:Admin API 文档(Architecture-and-Code-Map / 相关页)更新列表参数与字段。 - 无前置依赖;与 #381(SYB 已取消订单)都改 `purchase/` 包但不同文件区域,建议串行合并。 ## 验收标准 - [ ] 迁移在空库与存量库上执行成功;旧行 `created_by` 为 NULL、`created_source` 为空。 - [ ] 单元测试:各创建入口写入正确的创建人与来源;幂等重放不改写;Agent 重试来源为 `agent` 且创建人为空。 - [ ] 单元测试:`deviceId`(含 `unassigned`)、`createdBy`(含 `agent`、`unrecorded`)筛选及与现有筛选组合;非法值返回 400;filter-options 只返回实际出现的设备与创建人,权限与列表一致。 - [ ] Web:两个筛选可用并参与重置与导出;「创建人」列与详情显示正确;前端构建与相关测试通过。 - [ ] 发布(含迁移)需用户另行授权。 ## 风险与回退 - 数据库迁移(新增列与索引):高风险类别,实施和发布前再次确认;只新增可空列,不锁改既有数据。 - 历史任务无创建人,按创建人筛选只覆盖上线后的新任务。 - 回退:回滚代码即可(新增列保留不影响旧代码);如需删除列另行评估。 ## 文档影响 - 更新 Wiki Architecture-and-Code-Map(Admin 采购任务列表参数、filter-options 接口、`created_by`/`created_source` 字段)与 Business-Rules-and-Glossary(创建人与来源的记录规则、历史显示「未记录」、Agent 发起无创建人)。 - 先改线上 Wiki 并回读 revision,再执行一次 sync 与 sync --check。
Author
Owner

开始实施(2026-10-12,Claude Code)

用户:「按照顺序安排subagent用sonnet做#381 #382 #383。」(视为同意实施本单含迁移的代码;执行迁移与发布仍需用户另行授权。)

  • worktree D:/OPC/goauto-worktrees/issue-382,分支 feat/382-purchase-creator-filters,基线 origin/main 16e3d35(与 #381 独立,合并时串行)。
  • 由 Sonnet 子 Agent 按正文实施,Claude Code 审查并复跑测试。Gitea MCP 不可用,回退 REST。
## 开始实施(2026-10-12,Claude Code) 用户:「按照顺序安排subagent用sonnet做#381 #382 #383。」(视为同意实施本单含迁移的代码;**执行迁移与发布仍需用户另行授权**。) - worktree `D:/OPC/goauto-worktrees/issue-382`,分支 `feat/382-purchase-creator-filters`,基线 `origin/main` `16e3d35`(与 #381 独立,合并时串行)。 - 由 Sonnet 子 Agent 按正文实施,Claude Code 审查并复跑测试。Gitea MCP 不可用,回退 REST。
Author
Owner

实现回写(2026-10-12,Claude Code)

用户授权:「按照顺序做1,2,3,4。」。Sonnet 子 Agent 实施,Claude Code 审查复跑。Gitea MCP 不可用,回退 REST。

  • 分支 feat/382-purchase-creator-filters 已推送:6cc3bb9(迁移+模型)、ac7f811(purchase 包:写入、筛选、filter-options)、9e54263(Web)。13 文件 +758/−8。
  • 迁移 1791700000000_purchase_task_creator.go:created_by(可空,索引 idx_purchase_task_created_by)、created_source(varchar(16) 默认 '');逐步判断已存在,可重跑;不改已有行。仅在 sqlite 测试中验证。
  • 写入:Admin 创建/备货/批量 → admin + JWT 用户;客户端密钥 → client_api + AuthorizedBy;Agent 重试 → agent、创建人空;操作人 0 存 NULL;幂等重放不改写。
  • 列表:deviceId(数字/unassigned)、createdBy(数字/agent/unrecorded);返回 createdBy/createdByName/createdSource;GET /api/admin/v1/purchase-tasks/filter-options(allowedOperator,路由在 /:taskId 之前)。
  • Web:设备、创建人下拉(参与查询/重置/导出),「创建人」列与详情。
  • 偏离与更正:① Admin BatchRetry 实为原地 Reset,不新建任务,不写创建人(正文已更正);② 非法筛选值沿用 CodeInvalidRequest 返回 422(非 400)。
  • 测试:迁移 TestMigratePurchaseTaskCreatorAddsColumnsKeepsLegacyRowsAndIsIdempotent;purchase creator_filter_test.go 11 项(写入、重放、Agent、客户端、筛选组合与非法值、filter-options 内容/空/权限、路由不冲突)。Claude Code 复核:go test -count=1 ./app/goauto/purchase/ ./cmd/migrate/... 通过,go build 通过;改动 Web 文件 eslint 结果与基线一致(24 错误/11 警告均为旧问题),build:prod 成功。
  • 风险:迁移发布需授权;MySQL 加索引耗时未实测;删除用户显示「用户 #id」。偶发并发用例 TestOrderBackfillConcurrentResolveUnknown 失败一次后 3 次复跑通过,与本单无关。
## 实现回写(2026-10-12,Claude Code) 用户授权:「按照顺序做1,2,3,4。」。Sonnet 子 Agent 实施,Claude Code 审查复跑。Gitea MCP 不可用,回退 REST。 - 分支 `feat/382-purchase-creator-filters` 已推送:`6cc3bb9`(迁移+模型)、`ac7f811`(purchase 包:写入、筛选、filter-options)、`9e54263`(Web)。13 文件 +758/−8。 - 迁移 `1791700000000_purchase_task_creator.go`:`created_by`(可空,索引 `idx_purchase_task_created_by`)、`created_source`(varchar(16) 默认 `''`);逐步判断已存在,可重跑;不改已有行。仅在 sqlite 测试中验证。 - 写入:Admin 创建/备货/批量 → `admin` + JWT 用户;客户端密钥 → `client_api` + `AuthorizedBy`;Agent 重试 → `agent`、创建人空;操作人 0 存 NULL;幂等重放不改写。 - 列表:`deviceId`(数字/`unassigned`)、`createdBy`(数字/`agent`/`unrecorded`);返回 `createdBy/createdByName/createdSource`;`GET /api/admin/v1/purchase-tasks/filter-options`(`allowedOperator`,路由在 `/:taskId` 之前)。 - Web:设备、创建人下拉(参与查询/重置/导出),「创建人」列与详情。 - 偏离与更正:① Admin `BatchRetry` 实为原地 `Reset`,不新建任务,不写创建人(正文已更正);② 非法筛选值沿用 `CodeInvalidRequest` 返回 422(非 400)。 - 测试:迁移 `TestMigratePurchaseTaskCreatorAddsColumnsKeepsLegacyRowsAndIsIdempotent`;purchase `creator_filter_test.go` 11 项(写入、重放、Agent、客户端、筛选组合与非法值、filter-options 内容/空/权限、路由不冲突)。Claude Code 复核:`go test -count=1 ./app/goauto/purchase/ ./cmd/migrate/...` 通过,`go build` 通过;改动 Web 文件 eslint 结果与基线一致(24 错误/11 警告均为旧问题),`build:prod` 成功。 - 风险:迁移发布需授权;MySQL 加索引耗时未实测;删除用户显示「用户 #id」。偶发并发用例 `TestOrderBackfillConcurrentResolveUnknown` 失败一次后 3 次复跑通过,与本单无关。
Author
Owner

合并、发布与 Wiki(2026-10-12,Claude Code)

用户授权:「按照顺序做1,2,3,4。如果线上有采集和采购任务进行着,等待无任务时再重启」;发布前用户选择「A」(按 main 发布,含 #378)。Gitea MCP 不可用,回退 REST。

  • 本单 Wiki:Business-Rules 新增「采购任务创建人与采购管理筛选(#382)」;Architecture 新增迁移、字段、参数与 filter-options 接口。
  • 待业务验收:新建采购任务后创建人显示正确;设备/创建人筛选与导出;历史任务显示「未记录」。
  • 合并:干净 worktree 基于 origin/main 16e3d35,依次 --no-ff 合并 #381 → c5709a0、#382 → 0893b55、#383 → 15bab9c,无冲突,已推送。合并后 go test ./... 除既有失败外通过(sybimport 基线已有失败;apprelease TestParseBuiltAgentAPKWhenAvailable 为本地有 Debug APK 时才运行的过时用例),Linux 构建与 Web build:prod 通过。
  • 发布(122.228.200.167):用户选择按 main 发布(含此前未发布的 #378 Server 与迁移)。目录 /home/goauto/releases/20261012-15bab9c-381-382-383;数据库备份 /home/goauto/backups/20261012-381-382-383;迁移 1791600000000、1791700000000 执行成功并回读;迁移前与切换前无执行中的采集/采购/同步/AI/自动匹配任务;current 原子切换并重启,active。server SHA256 f3486444…ff3695(进程一致),index.html a4c85aa8…d5853。
  • 按内容验收:首页/登录页为新 index,10 项 JS/CSS 200,验证码 200,未登录业务接口(含 filter-options)401,日志无 panic/fatal/1146/1054;采购1/2 心跳恢复在线。
  • 回滚:current 切回 /home/goauto/releases/20261010-3d5e964-372v3-web 并重启;新增列/表保留。
  • Wiki:Business-Rules-and-Glossary c8aa5a4716d0b9a462677b1276a7362d7227c95c、Architecture-and-Code-Map 72fa776c9adb4993c84918494b37516f3156d354、Deployment-and-Operations 4103d9b6174bdba2886c4916e85e0763ce2dcd53,回读一致;sync 与 sync --check 各一次通过,镜像 ac112b5 已推送 main。
## 合并、发布与 Wiki(2026-10-12,Claude Code) 用户授权:「按照顺序做1,2,3,4。如果线上有采集和采购任务进行着,等待无任务时再重启」;发布前用户选择「A」(按 main 发布,含 #378)。Gitea MCP 不可用,回退 REST。 - 本单 Wiki:Business-Rules 新增「采购任务创建人与采购管理筛选(#382)」;Architecture 新增迁移、字段、参数与 filter-options 接口。 - 待业务验收:新建采购任务后创建人显示正确;设备/创建人筛选与导出;历史任务显示「未记录」。 - 合并:干净 worktree 基于 `origin/main` `16e3d35`,依次 `--no-ff` 合并 #381 → `c5709a0`、#382 → `0893b55`、#383 → `15bab9c`,无冲突,已推送。合并后 `go test ./...` 除既有失败外通过(`sybimport` 基线已有失败;`apprelease` `TestParseBuiltAgentAPKWhenAvailable` 为本地有 Debug APK 时才运行的过时用例),Linux 构建与 Web `build:prod` 通过。 - 发布(122.228.200.167):用户选择按 main 发布(含此前未发布的 #378 Server 与迁移)。目录 `/home/goauto/releases/20261012-15bab9c-381-382-383`;数据库备份 `/home/goauto/backups/20261012-381-382-383`;迁移 `1791600000000`、`1791700000000` 执行成功并回读;迁移前与切换前无执行中的采集/采购/同步/AI/自动匹配任务;current 原子切换并重启,active。server SHA256 `f3486444…ff3695`(进程一致),index.html `a4c85aa8…d5853`。 - 按内容验收:首页/登录页为新 index,10 项 JS/CSS 200,验证码 200,未登录业务接口(含 `filter-options`)401,日志无 panic/fatal/1146/1054;采购1/2 心跳恢复在线。 - 回滚:current 切回 `/home/goauto/releases/20261010-3d5e964-372v3-web` 并重启;新增列/表保留。 - Wiki:Business-Rules-and-Glossary `c8aa5a4716d0b9a462677b1276a7362d7227c95c`、Architecture-and-Code-Map `72fa776c9adb4993c84918494b37516f3156d354`、Deployment-and-Operations `4103d9b6174bdba2886c4916e85e0763ce2dcd53`,回读一致;`sync` 与 `sync --check` 各一次通过,镜像 `ac112b5` 已推送 main。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#382