2026-06-13-production-task-assignee-my-tasks.md 18 KB

生产任务负责人、转发与「我的生产任务」实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 派发生产任务时指定负责人;新增「我的生产任务」双 Tab 页(当前 / 历史);支持任意状态下转发与管理员改派;我的任务页严格按负责人过滤,工单页保持管理覆盖。

Architecture: biz_production_task.current_assignee_user_id 存当前唯一负责人;biz_production_task_handover 记录转发/改派历史。API 层复用 QC 的 operateScope=mine|manage 鉴权模式。前端复用 WorkOrderTaskSubList + TaskExecutionDialog,新增 my-production-task/index.vue。

Tech Stack: MySQL 5.7 · FastAPI · SQLAlchemy · Vue 3 · Element Plus · pnpm

Design Spec: docs/superpowers/specs/2026-06-13-production-task-assignee-my-tasks-design.md


File Map

文件 职责
docs/sql/migrations/2026-06-13-production-task-assignee.sql 任务表字段 + handover 表
docs/sql/migrations/2026-06-13-my-production-task-menu.sql 菜单 INSERT
backend/app/models/models.py BizProductionTaskHandover 模型 + 任务字段
backend/app/crud/production_task.py create/list_mine/forward/reassign/assert_assignee
backend/app/schemas/production_task.py Create/Forward/Reassign body
backend/app/api/production.py 新路由 + operateScope 挂到写接口
backend/app/api/menu.py 菜单种子
web-admin/src/api/production-task.ts mine/forward/reassign API
web-admin/src/views/production/my-production-task/index.vue 双 Tab 页面
web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue 派发选负责人
web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue listMode + 负责人展示 + 改派/转发
web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue operateScope + 转发按钮
web-admin/src/views/production/production-work-order/ForwardTaskDialog.vue 新建 转发/改派共用弹窗
web-admin/src/router/modules/sqxp.ts 路由注册
web-admin/src/App.vue panelTitleBarTextRules

Task 1: 数据库迁移

Files:

  • Create: docs/sql/migrations/2026-06-13-production-task-assignee.sql
  • Modify: docs/sql/zkxw.sql(同步表结构片段)

  • [ ] Step 1: 编写 migration SQL

-- docs/sql/migrations/2026-06-13-production-task-assignee.sql
ALTER TABLE `biz_production_task`
  ADD COLUMN `current_assignee_user_id` varchar(64) DEFAULT NULL
  COMMENT '当前负责人用户ID' AFTER `task_status`;

CREATE TABLE IF NOT EXISTS `biz_production_task_handover` (
  `id` varchar(64) NOT NULL COMMENT '主键',
  `task_id` varchar(64) NOT NULL COMMENT '生产任务ID',
  `from_user_id` varchar(64) NOT NULL COMMENT '转出用户ID',
  `to_user_id` varchar(64) NOT NULL COMMENT '接收用户ID',
  `handover_time` datetime NOT NULL COMMENT '转发时间',
  `remark` varchar(512) DEFAULT NULL COMMENT '转发说明',
  `tenant_no` varchar(64) NOT NULL COMMENT '租户编号',
  `order_num` int DEFAULT 0,
  `create_by` varchar(64) DEFAULT NULL,
  `create_dept_id` varchar(64) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_by` varchar(64) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `del_flag` tinyint DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_prod_task_handover_task` (`task_id`, `del_flag`),
  KEY `idx_prod_task_handover_from` (`from_user_id`, `tenant_no`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生产任务转发记录';
  • Step 2: 在目标库执行 migration

Run: 在测试库执行上述 SQL
Expected: biz_production_task 新增列;biz_production_task_handover 表存在

  • Step 3: 同步 docs/sql/zkxw.sql 对应 DDL

Task 2: Backend 模型与 Schema

Files:

  • Modify: backend/app/models/models.py
  • Modify: backend/app/schemas/production_task.py

  • [ ] Step 1: BizProductionTask 增加列

在 BizProductionTask 的 task_status 后添加:

current_assignee_user_id = Column(String(64), nullable=True, comment="当前负责人用户ID")
  • Step 2: 新增 BizProductionTaskHandover 模型

参照 design spec §5.3,放在 BizProductionTask 附近。

  • Step 3: 扩展 Schema

backend/app/schemas/production_task.py:

class ProductionTaskCreate(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    work_order_id: str
    assignee_user_id: str = Field(..., alias="assigneeUserId")
    task_type: Optional[str] = 'cell_storage'
    task_name: Optional[str] = None
    route_id: Optional[str] = None
    description: Optional[str] = None
    remark: Optional[str] = None


class ProductionTaskForwardBody(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    to_user_id: str = Field(..., alias="toUserId")
    remark: Optional[str] = None


class ProductionTaskAssigneeUpdate(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    assignee_user_id: str = Field(..., alias="assigneeUserId")
    remark: Optional[str] = None

Task 3: CRUD — 负责人、我的任务、转发、改派

Files:

  • Modify: backend/app/crud/production_task.py

  • [ ] Step 1: 扩展 _task_to_dict

增加字段(批量 list 时由 enrich 函数补充姓名):

"currentAssigneeUserId": t.current_assignee_user_id or "",
"currentAssigneeUserName": "",  # enrich 填充
  • Step 2: 用户校验 helper
def _validate_tenant_user_ids(db: Session, user_ids: List[str], tenant_no: str) -> None:
    # 去重、非空;查询 sys_user 有效且 tenant_no 匹配;缺失则 raise ValueError
  • [ ] Step 3: 修改 create_production_task

  • body 读取 assignee_user_id,校验必填;

  • 创建时设置 task.current_assignee_user_id = assignee_user_id。

  • [ ] Step 4: 新增 assert_task_assignee

def assert_task_assignee(db, task_id: str, user_id: str, tenant_no: str) -> BizProductionTask:
    task = get_task(...)
    if (task.current_assignee_user_id or "") != user_id:
        raise PermissionError("非当前任务负责人,无权操作")
    return task
  • Step 5: 新增 forward_production_task
def forward_production_task(
    db, task_id, *, from_user_id, to_user_id, tenant_no, operator_id, create_dept_id, remark=None
) -> BizProductionTask:
    if from_user_id == to_user_id:
        raise ValueError("不能转发给自己")
    _validate_tenant_user_ids(db, [to_user_id], tenant_no)
    task = assert_task_assignee(db, task_id, from_user_id, tenant_no)  # mine 场景
    # 插入 BizProductionTaskHandover
    task.current_assignee_user_id = to_user_id
    task.update_by = operator_id
    task.update_time = datetime.now()
    db.add(task)
    return task
  • [ ] Step 6: 新增 reassign_production_task(管理员改派)

  • 不强制 assert 当前用户是 assignee;

  • from_user_id = 原 current_assignee_user_id 或 operator_id;

  • remark 默认「管理员改派」;

  • 同样写 handover + 更新 current。

  • [ ] Step 7: 新增 list_mine_production_tasks

Query 参数:scope=current|history

# current: current_assignee_user_id == assignee_user_id
# history: EXISTS handover WHERE from_user_id == me AND current_assignee_user_id != me
  • 复用 list_production_tasks 的 keyword / task_status / 工单 enrich 逻辑(可抽 _enrich_task_rows)。

  • [ ] Step 8: _enrich_assignee_names

批量查 sys_user 填 currentAssigneeUserName;history 列表额外填 handoverTime(子查询 MAX handover_time where from_user_id=me)。

  • Step 9: 字段一致性

Run: python scripts/check_field_consistency.py
Expected: production_task 相关无缺失字段告警


Task 4: API 路由与 operateScope 鉴权

Files:

  • Modify: backend/app/api/production.py

  • [ ] Step 1: 新增 _assert_production_task_write_access

参照 backend/app/api/qc.py 的 _assert_task_write_access:

def _assert_production_task_write_access(db, user, task_id, tenant_no, operate_scope):
    scope = (operate_scope or "manage").strip().lower()
    user_id = user.get("user_id")
    role = user.get("role")
    codes = get_user_menu_codes(db, user_id) if role != SUPER_ADMIN_ROLE else set()
    if scope == "mine":
        if role != SUPER_ADMIN_ROLE and "production:my-task:list" not in codes:
            raise PermissionError("无菜单访问权限")
        production_task_svc.assert_task_assignee(db, task_id, user_id, tenant_no)
    elif role != SUPER_ADMIN_ROLE and "production:work-order:list" not in codes:
        raise PermissionError("无菜单访问权限")
  • Step 2: 新路由
Method Path 权限
GET /production-tasks/mine require_menu_code("production:my-task:list")
POST /production-tasks/{id}/forward require_any_menu_code("production:my-task:list", "production:work-order:list")
PUT /production-tasks/{id}/assignee require_menu_code("production:work-order:list")
  • forward:operateScope=mine 时 assert assignee;manage 时仅校验 work-order list。

  • [ ] Step 3: 为写接口挂 operateScope

在以下端点增加 operateScope: Optional[str] = Query(None) 并调用 _assert_production_task_write_access:

  • PUT /production-tasks/{id}/status
  • POST|PUT|DELETE steps 相关
  • PUT step reagents/consumables/data-collection/outputs
  • POST inputs/outputs

GET 详情保持不变(mine 页只读也允许看)。

  • Step 4: 创建任务 API 校验 assigneeUserId

ProductionTaskCreate.assignee_user_id 必填,400 若缺失或用户无效。


Task 5: 菜单与路由

Files:

  • Create: docs/sql/migrations/2026-06-13-my-production-task-menu.sql
  • Modify: docs/sql/zkxw.sql
  • Modify: backend/app/api/menu.py
  • Modify: web-admin/src/router/modules/sqxp.ts
  • Modify: web-admin/src/App.vue

  • [ ] Step 1: 菜单 SQL

INSERT INTO `sys_menu` (...) VALUES (
  '94000000000000000000000000000001', '我的生产任务', 'PRM00000000000000000000000000001', 'C',
  'my-production-task', '/production/my-production-task/index',
  'production:my-task:list',
  1, '1', '1', 'zkxw', 4, NULL, ...
) ON DUPLICATE KEY UPDATE ...;

parent_id = PRM00000000000000000000000000001(生产管理),order_num = 4(生产工单之后)。

  • Step 2: menu.py 生产管理 children 增加
{
    "path": "my-production-task",
    "component": "/production/my-production-task/index",
    "meta": {
        "title": "我的生产任务",
        "menuCode": "production:my-task:list",
    },
},
  • Step 3: sqxp.ts 路由

在 production-work-order 路由后添加 my-production-task 子路由,component 指向新页面。

  • Step 4: App.vue panelTitleBarTextRules

添加 '我的生产任务'。


Task 6: 前端 API 层

Files:

  • Modify: web-admin/src/api/production-task.ts

  • [ ] Step 1: 类型与 operateScope

export type ProductionTaskOperateScope = 'mine' | 'manage'

export function fetchMyProductionTasks(params: {
  page?: number
  pageSize?: number
  keyword?: string
  taskStatus?: string
  workOrderType?: string
  scope: 'current' | 'history'
}) {
  return request.get({ url: '/web/admin/production-tasks/mine', params })
}

export function fetchForwardProductionTask(
  id: string,
  data: { toUserId: string; remark?: string },
  operateScope?: ProductionTaskOperateScope
) {
  return request.post({
    url: `/web/admin/production-tasks/${id}/forward`,
    params: data,
    ...(operateScope === 'mine' ? { params: { ...data, operateScope: 'mine' } } : {})
  })
}

export function fetchReassignProductionTask(id: string, data: { assigneeUserId: string; remark?: string }) {
  return request.put({ url: `/web/admin/production-tasks/${id}/assignee`, params: data })
}
  • Step 2: 现有写 API 增加可选 operateScope

参照 web-admin/src/api/qc-task.ts,mine 模式请求带 operateScope=mine:

  • fetchUpdateProductionTaskStatus
  • step CRUD 等(TaskExecutionDialog 使用的全部写接口)

  • [ ] Step 3: fetchCreateProductionTask 增加 assigneeUserId


Task 7: 派发弹窗 — 选择负责人

Files:

  • Modify: web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue
  • Create: web-admin/src/composables/useTenantUserOptions.ts(若 QC 侧无共用 composable 则新建)

  • [ ] Step 1: form 增加 assigneeUserId

const form = reactive({
  taskName: '',
  routeId: '',
  description: '',
  remark: '',
  assigneeUserId: ''
})
const rules = {
  taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
  assigneeUserId: [{ required: true, message: '请选择负责人', trigger: 'change' }]
}
  • Step 2: UI — 负责人 el-select

form-grid-2 内、taskName 下方或同排:

<el-form-item label="负责人" prop="assigneeUserId" class="form-col-span-2">
  <el-select v-model="form.assigneeUserId" filterable placeholder="请选择负责人" style="width: 100%">
    <el-option
      v-for="u in userOptions"
      :key="u.id"
      :label="`${u.nickName}(${u.userNo})`"
      :value="u.id"
    />
  </el-select>
</el-form-item>
  • [ ] Step 3: handleCreateTask 传 assigneeUserId

  • [ ] Step 4: 弹窗打开时 loadUserOptions()

复用 QC 配置检测的用户列表 API(如 fetchUserList from admin API)。


Task 8: ForwardTaskDialog + WorkOrderTaskSubList 改造

Files:

  • Create: web-admin/src/views/production/production-work-order/ForwardTaskDialog.vue
  • Modify: web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue

  • [ ] Step 1: 新建 ForwardTaskDialog.vue

Props: visible, taskId, mode: 'forward' | 'reassign', operateScope
Emit: success
表单:下一负责人 + 备注;提交调 forward 或 reassign API。

  • Step 2: WorkOrderTaskSubList 增加 props
listMode?: 'workOrder' | 'mine-current' | 'mine-history'
mineFilters?: { keyword, taskStatus, workOrderType }
page?, pageSize?, refreshToken?
  • workOrder:原逻辑,fetchProductionTaskList({ workOrderId })
  • mine-*:调 fetchMyProductionTasks({ scope: 'current'|'history', ...mineFilters, page, pageSize })
  • emit update:total 供分页

  • [ ] Step 3: 卡片 meta 展示负责人

<span class="work-order-task-card__meta-item">
  负责人:{{ row.currentAssigneeUserName || '—' }}
</span>
  • Step 4: 操作按钮
listMode 按钮
mine-current 执行/查看 + 转发(当前用户是 assignee 时)
mine-history 仅查看
workOrder 现有 + 改派(dropdown)
  • Step 5: operateScope computed
const operateScope = computed(() =>
  props.listMode?.startsWith('mine') ? 'mine' : 'manage'
)
  • Step 6: 写操作传 operateScope

进入 TaskExecutionDialog 时传入 :operate-scope="operateScope"。


Task 9: TaskExecutionDialog — operateScope + 转发

Files:

  • Modify: web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue

  • [ ] Step 1: props

operateScope?: 'mine' | 'manage' | 'history'  // history 强制 readonly
readonly?: boolean  // 已有;history 模式设为 true
  • Step 2: 工具栏「转发任务」

非 readonly 且 operateScope !== 'history' 时显示;任意 taskStatus 均可(D3)。

  • Step 3: editor composable 写请求带 operateScope

找到 task execution 使用的 status/step API 调用,mine 时附加 query。

  • Step 4: 转发成功后 emit refresh

Task 10: 我的生产任务页面

Files:

  • Create: web-admin/src/views/production/my-production-task/index.vue

  • [ ] Step 1: 页面骨架

参照 my-qc-task/index.vue:

  • panel-top 标题「我的生产任务」
  • el-tabs:当前任务 / 历史执行
  • 共用 filter-block(关键词、任务状态、工单类型)
  • WorkOrderTaskSubList with listMode="mine-current" / "mine-history"
  • 分页 list-pager-bar

  • [ ] Step 2: Tab 切换重置 page 并 refresh

  • [ ] Step 3: filter hint 文案

  • 当前:仅展示当前指派给您的生产任务。

  • 历史:展示您曾负责并已转出的任务,仅可查看。

  • [ ] Step 4: 高度自适应

复用 my-qc-task 的 ResizeObserver 逻辑。


Task 11: 回归与图谱

  • Step 1: 手工测试清单(design spec §8 全部场景)
场景 预期
派发未选负责人 400
A 当前可见,B 不可见 ✓
dpz/jxz/ywc 均可转发 ✓
A 转发后历史 Tab 只读 ✓
管理员改派 handover 有记录;新负责人当前可见
mine 非 assignee 写操作 403
工单页 manage 督办 成功
  • Step 2: 更新知识图谱

Run: ./scripts/update-graph.sh backend frontend

  • Step 3: lint

Run: pnpm lint(web-admin 改动文件)
Expected: 无新增 error


Spec Coverage Checklist

Spec 章节 Task
§4.1 派发选负责人 Task 7
§4.2 工单展示负责人 + 改派 Task 8
§4.3 执行 + 转发 Task 9
§4.4 我的生产任务双 Tab Task 10
§4.5 菜单 Task 5
§5 数据模型 Task 1–2
§6 API Task 3–4
§6.3 operateScope Task 4, 6, 9
§6.4 管理员改派 Task 3, 8
D1–D10 全部决策 已写入 spec,本 plan 全覆盖

不在本 Plan 范围

  • 消息通知
  • 小程序端
  • 历史任务批量回填脚本