2026-08-17-production-pre-transit-sample.md 15 KB

生产管理 — 预产及在途样本独立副本 Implementation Plan

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 (- [x]) syntax for tracking.

Goal: 在【生产管理】下新增与质检侧代码独立的「预产及在途样本」页面与菜单,共用现有查询 API,并自动授权给已有生产管理子菜单的角色。

Architecture: 完整复制 quality-inspection/pre-transit-sample 到 production/pre-transit-sample;路由挂在 production-group/production、库位管理之前;权限码 production:preTransitSample:list;后端两列表接口改为 require_any_menu_code(质检码 OR 生产码);SQL 幂等插菜单并按 PRM 子菜单授权。

Tech Stack: Vue 3 + Element Plus(web-admin)、FastAPI menu_guard、MySQL sys_menu / sys_role_menu

Spec: docs/superpowers/specs/2026-08-17-production-pre-transit-sample-design.md


File map

文件 职责
web-admin/src/views/production/pre-transit-sample/index.vue 生产侧页面壳(自质检复制)
web-admin/src/views/production/pre-transit-sample/PreTransitProtocolCardList.vue 生产侧卡片列表(自质检复制)
web-admin/src/router/modules/sqxp.ts 注册生产侧路由
backend/app/api/pre_transit_sample.py 共用 API 鉴权 OR
docs/sql/alter_sys_menu_add_production_pre_transit_sample.sql 菜单 + 角色授权迁移
docs/sql/zkxw.sql 基线插入两条 sys_menu
待同步到生产的sql脚本 追加待上线 SQL 片段

不改:web-admin/src/api/pre-transit-sample.ts、质检侧 views/quality-inspection/pre-transit-sample/**。

ID / 权限常量(全任务统一)

常量 值
C 菜单 id 95200000000000000000000000000001
F 按钮 id 95200000000000000000000000000002
parent_id PRM00000000000000000000000000001
perms / menuCode production:preTransitSample:list
order_num(C) 0
组件 path /production/pre-transit-sample/index
路由 Hash /#/production-group/production/pre-transit-sample

Task 1: 复制前端页面到生产目录

Files:

  • Create: web-admin/src/views/production/pre-transit-sample/index.vue
  • Create: web-admin/src/views/production/pre-transit-sample/PreTransitProtocolCardList.vue

  • [x] Step 1: 复制目录

mkdir -p web-admin/src/views/production/pre-transit-sample
cp web-admin/src/views/quality-inspection/pre-transit-sample/index.vue \
  web-admin/src/views/production/pre-transit-sample/index.vue
cp web-admin/src/views/quality-inspection/pre-transit-sample/PreTransitProtocolCardList.vue \
  web-admin/src/views/production/pre-transit-sample/PreTransitProtocolCardList.vue

Expected: 两文件存在且行数与源文件接近(卡片约 1422 行)。

  • Step 2: 改 index.vue 注释与组件名

将文件头注释改为:

<!-- 生产管理 — 预产及在途样本 -->

将:

defineOptions({ name: 'QualityInspectionPreTransitSample' })

改为:

defineOptions({ name: 'ProductionPreTransitSample' })

(keep-alive 需唯一组件名;勿与质检侧同名。)

其余逻辑、@/api/pre-transit-sample import、弹窗路径保持不变。PreTransitProtocolCardList.vue 仅改文件头注释为「生产管理 — …」即可,相对 import ./ 无需改。

  • Step 3: 确认无交叉引用质检目录
rg -n "quality-inspection/pre-transit" web-admin/src/views/production/pre-transit-sample || true

Expected: 无匹配。

  • Step 4: Commit
git add web-admin/src/views/production/pre-transit-sample
git commit -m "$(cat <<'EOF'
feat: copy pre-transit sample views under production.

EOF
)"

Task 2: 注册生产侧路由

Files:

  • Modify: web-admin/src/router/modules/sqxp.ts(ProductionManage.children,库位管理节点之前)

  • [x] Step 1: 在库位管理前插入路由

定位 name: 'ProductionStorageFacility'(约 L682)。在其之前插入:

          {
            id: '95200000000000000000000000000001',
            name: 'ProductionPreTransitSample',
            path: 'pre-transit-sample',
            component: '/production/pre-transit-sample/index',
            meta: {
              title: '预产及在途样本',
              keepAlive: true,
              menuCode: 'production:preTransitSample:list'
            }
          },

保持其后的 ProductionStorageFacility 块不变。

  • Step 2: 核对顺序
rg -n "ProductionPreTransitSample|ProductionStorageFacility" web-admin/src/router/modules/sqxp.ts

Expected: ProductionPreTransitSample 行号小于 ProductionStorageFacility(在同一 children 内)。

  • Step 3: Commit
git add web-admin/src/router/modules/sqxp.ts
git commit -m "$(cat <<'EOF'
feat: register production pre-transit sample route.

EOF
)"

Task 3: 后端共用 API 接受生产权限码

Files:

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

  • [x] Step 1: 改 import 与两处 Depends

将:

from app.core.menu_guard import require_menu_code

改为:

from app.core.menu_guard import require_any_menu_code

将 list_in_transit_samples 与 list_pre_delivery_samples 中的:

user: dict = Depends(require_menu_code("biz:preTransitSample:list")),

均改为:

user: dict = Depends(
        require_any_menu_code(
            "biz:preTransitSample:list",
            "production:preTransitSample:list",
        )
    ),
  • Step 2: 静态核对
rg -n "require_menu_code|require_any_menu_code|production:preTransitSample" backend/app/api/pre_transit_sample.py

Expected: 无 require_menu_code;两处 require_any_menu_code;含 production:preTransitSample:list。

  • Step 3: Commit
git add backend/app/api/pre_transit_sample.py
git commit -m "$(cat <<'EOF'
fix: allow production perm on shared pre-transit APIs.

EOF
)"

Task 4: 菜单与角色授权 SQL

Files:

  • Create: docs/sql/alter_sys_menu_add_production_pre_transit_sample.sql
  • Modify: docs/sql/zkxw.sql(插入两条 sys_menu)
  • Modify: 待同步到生产的sql脚本(追加可执行片段)

  • [x] Step 1: 写迁移脚本

创建 docs/sql/alter_sys_menu_add_production_pre_transit_sample.sql,全文如下:

-- 生产管理:预产及在途样本(独立页面 + 独立权限码)
-- 幂等:可重复执行
-- 授权:已有【生产管理】PRM 下任一有效子菜单的角色 → 授予本 C/F,并补授 PRM 祖先

SET @tenant_no = CONVERT('zkxw' USING utf8mb4) COLLATE utf8mb4_unicode_ci;
SET @prm_id = CONVERT('PRM00000000000000000000000000001' USING utf8mb4) COLLATE utf8mb4_unicode_ci;
SET @menu_c = CONVERT('95200000000000000000000000000001' USING utf8mb4) COLLATE utf8mb4_unicode_ci;
SET @menu_f = CONVERT('95200000000000000000000000000002' USING utf8mb4) COLLATE utf8mb4_unicode_ci;

-- 1) C 菜单
INSERT INTO sys_menu (
  id, menu_name, parent_id, menu_type, path, component, perms,
  is_frame, visible, status, tenant_no, order_num, remark,
  create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
  @menu_c,
  '预产及在途样本',
  @prm_id,
  'C',
  'pre-transit-sample',
  '/production/pre-transit-sample/index',
  'production:preTransitSample:list',
  1, '1', '1', @tenant_no, 0,
  '生产看板:在途样本与预产协议(协议单维度只读;与质检页代码独立)',
  NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
  SELECT 1 FROM sys_menu m
  WHERE m.id COLLATE utf8mb4_unicode_ci = @menu_c
);

UPDATE sys_menu
SET
  menu_name = '预产及在途样本',
  parent_id = @prm_id,
  menu_type = 'C',
  path = 'pre-transit-sample',
  component = '/production/pre-transit-sample/index',
  perms = 'production:preTransitSample:list',
  visible = '1',
  status = '1',
  order_num = 0,
  remark = '生产看板:在途样本与预产协议(协议单维度只读;与质检页代码独立)',
  del_flag = 0,
  update_time = NOW()
WHERE id COLLATE utf8mb4_unicode_ci = @menu_c;

-- 2) F 按钮
INSERT INTO sys_menu (
  id, menu_name, parent_id, menu_type, path, component, perms,
  is_frame, visible, status, tenant_no, order_num, remark,
  create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
  @menu_f,
  '预产及在途查询',
  @menu_c,
  'F',
  '',
  '',
  'production:preTransitSample:list',
  1, '0', '1', @tenant_no, 1,
  '生产侧预产及在途样本列表查询',
  NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
  SELECT 1 FROM sys_menu m
  WHERE m.id COLLATE utf8mb4_unicode_ci = @menu_f
);

UPDATE sys_menu
SET
  menu_name = '预产及在途查询',
  parent_id = @menu_c,
  menu_type = 'F',
  path = '',
  component = '',
  perms = 'production:preTransitSample:list',
  visible = '0',
  status = '1',
  order_num = 1,
  remark = '生产侧预产及在途样本列表查询',
  del_flag = 0,
  update_time = NOW()
WHERE id COLLATE utf8mb4_unicode_ci = @menu_f;

-- 3) 目标角色:已有 PRM 下任一有效子菜单授权
-- 3a) 恢复软删的 PRM / C / F 授权
UPDATE sys_role_menu rm
INNER JOIN sys_role r
  ON r.id COLLATE utf8mb4_unicode_ci = rm.role_id COLLATE utf8mb4_unicode_ci
 AND r.del_flag = 0
 AND r.tenant_no COLLATE utf8mb4_unicode_ci = @tenant_no
SET
  rm.del_flag = 0,
  rm.update_time = NOW(),
  rm.remark = 'grant production pre-transit to PRM roles'
WHERE rm.tenant_no COLLATE utf8mb4_unicode_ci = @tenant_no
  AND rm.del_flag = 1
  AND rm.menu_id COLLATE utf8mb4_unicode_ci IN (@prm_id, @menu_c, @menu_f)
  AND EXISTS (
    SELECT 1
    FROM sys_role_menu rm2
    INNER JOIN sys_menu m2
      ON m2.id COLLATE utf8mb4_unicode_ci = rm2.menu_id COLLATE utf8mb4_unicode_ci
     AND m2.del_flag = 0
     AND m2.parent_id COLLATE utf8mb4_unicode_ci = @prm_id
    WHERE rm2.role_id COLLATE utf8mb4_unicode_ci = r.id COLLATE utf8mb4_unicode_ci
      AND rm2.tenant_no COLLATE utf8mb4_unicode_ci = @tenant_no
      AND rm2.del_flag = 0
  );

-- 3b) 插入缺失授权(PRM + C + F)
INSERT INTO sys_role_menu (
  id, role_id, menu_id, tenant_no, order_num, remark,
  create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
  UPPER(MD5(CONCAT(r.id, '|', x.menu_id))),
  r.id,
  x.menu_id,
  @tenant_no,
  0,
  'grant production pre-transit to PRM roles',
  'admin',
  '',
  NOW(),
  NULL,
  NOW(),
  0
FROM sys_role r
INNER JOIN (
  SELECT @prm_id AS menu_id
  UNION ALL SELECT @menu_c
  UNION ALL SELECT @menu_f
) x
WHERE r.del_flag = 0
  AND r.tenant_no COLLATE utf8mb4_unicode_ci = @tenant_no
  AND EXISTS (
    SELECT 1
    FROM sys_role_menu rm2
    INNER JOIN sys_menu m2
      ON m2.id COLLATE utf8mb4_unicode_ci = rm2.menu_id COLLATE utf8mb4_unicode_ci
     AND m2.del_flag = 0
     AND m2.parent_id COLLATE utf8mb4_unicode_ci = @prm_id
    WHERE rm2.role_id COLLATE utf8mb4_unicode_ci = r.id COLLATE utf8mb4_unicode_ci
      AND rm2.tenant_no COLLATE utf8mb4_unicode_ci = @tenant_no
      AND rm2.del_flag = 0
  )
  AND EXISTS (
    SELECT 1 FROM sys_menu m
    WHERE m.id COLLATE utf8mb4_unicode_ci = x.menu_id
      AND m.del_flag = 0
  )
  AND NOT EXISTS (
    SELECT 1 FROM sys_role_menu rm
    WHERE rm.role_id COLLATE utf8mb4_unicode_ci = r.id COLLATE utf8mb4_unicode_ci
      AND rm.menu_id COLLATE utf8mb4_unicode_ci = x.menu_id
      AND rm.tenant_no COLLATE utf8mb4_unicode_ci = @tenant_no
      AND rm.del_flag = 0
  );
  • Step 2: 基线 zkxw.sql 插入两条菜单

在 sys_menu 插入段中、95000000000000000000000000000003(洁净服管理)之前或附近按 id 排序插入(建议紧挨 951… 段之前插入 952…):

INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('95200000000000000000000000000001', '预产及在途样本', 'PRM00000000000000000000000000001', 'C', 'pre-transit-sample', '/production/pre-transit-sample/index', 'production:preTransitSample:list', 1, '1', '1', 'zkxw', 0, '生产看板:在途样本与预产协议(协议单维度只读;与质检页代码独立)', NULL, '', '2026-08-17 12:00:00', NULL, '2026-08-17 12:00:00', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('95200000000000000000000000000002', '预产及在途查询', '95200000000000000000000000000001', 'F', '', '', 'production:preTransitSample:list', 1, '0', '1', 'zkxw', 1, '生产侧预产及在途样本列表查询', NULL, '', '2026-08-17 12:00:00', NULL, '2026-08-17 12:00:00', 0);

核对无重复 id:

rg "9520000000000000000000000000000" docs/sql/zkxw.sql

Expected: 恰好两行(C + F)。

  • Step 3: 追加到 待同步到生产的sql脚本

在文件末尾追加注释 + 与 Step 1 相同的可执行 SQL(或 SOURCE/-- 见 docs/sql/alter_sys_menu_add_production_pre_transit_sample.sql 并粘贴全文,与仓库现有「待同步」习惯一致:粘贴全文便于运维一次执行)。

  • Step 4: Commit
git add docs/sql/alter_sys_menu_add_production_pre_transit_sample.sql \
  docs/sql/zkxw.sql \
  待同步到生产的sql脚本
git commit -m "$(cat <<'EOF'
docs(sql): add production pre-transit sample menu and grants.

EOF
)"

Task 5: 图谱更新与手工验收

Files:

  • 运行:./scripts/update-graph.sh backend 与 ./scripts/update-graph.sh frontend(或 all)

  • [x] Step 1: 更新知识图谱

./scripts/update-graph.sh all

若图谱产物有变更则提交:

git add backend/graphify-out web-admin/graphify-out
git status --short
# 若有变更:
git commit -m "$(cat <<'EOF'
chore: update graphify after production pre-transit sample.

EOF
)"
  • Step 2: 本地手工验收清单
  1. 在本地/测试库执行 docs/sql/alter_sys_menu_add_production_pre_transit_sample.sql。
  2. 重新登录 web-admin(或刷新菜单权限缓存)。
  3. 打开 /#/production-group/production/pre-transit-sample:双分区列表可加载;与质检侧同筛选结果一致。
  4. 侧栏【生产管理】中「预产及在途样本」在【库位管理】上方。
  5. 质检路由 /#/quality-group/quality-manage/pre-transit-sample 仍正常。
  6. (可选)角色管理中确认已有 PRM 子菜单的角色已勾选新菜单;仅有生产基础信息、无 PRM 子菜单的角色未自动获得。
  • Step 3: 完成标记

将本 plan 中已完成 Task 的 - [x] 改为 - [x](若执行 agent 负责跟踪)。


Spec coverage (self-review)

Spec 要求 Task
完整复制 Vue 到 production Task 1
独立组件名 / 不引用质检目录 Task 1
路由在库位管理前、独立 menuCode Task 2
共用 API + OR 权限码 Task 3
菜单 ID 952…、SQL 幂等、PRM 角色授权、zkxw/待同步 Task 4
图谱 + 验收 Task 5
不改质检页、不新建业务查询 API 各 Task 未触及

无 TBD/占位符;ID 与权限码全文一致。